unit ProcessMon;
{$POINTERMATH ON}
interface
uses
nt_status, common;
function
_DriverEntry(p_DriverObject: PDRIVER_OBJECT;
pusRegistryPath: PUNICODE_STRING): NTSTATUS; stdcall;
implementation
uses
ntoskrnl, fcall, macros, ProcPath;
var
g_usDeviceName, g_usSymbolicLinkName: UNICODE_STRING;
g_pkEventObject: PKEVENT;
g_fbNotifyRoutineSet: Boolean;
g_ProcessData: PROCESS_DATA;
g_dwImageFileNameOffset: DWORD;
function
DispatchCreateClose(p_DeviceObject:PDEVICE_OBJECT; p_Irp:PIRP): NTSTATUS; stdcall;
begin
p_Irp^.IoStatus.Status := STATUS_SUCCESS;
p_Irp^.IoStatus.Information := 0;
IofCompleteRequest(p_Irp, IO_NO_INCREMENT);
result := STATUS_SUCCESS;
end;
procedure DriverUnload(pDriverObject:PDRIVER_OBJECT); stdcall;
begin
IoDeleteSymbolicLink(@g_usSymbolicLinkName);
IoDeleteDevice(pDriverObject^.DeviceObject);
end;
procedure ProcessNotifyRoutine(dwParentId:HANDLE; dwProcessId:HANDLE; bCreate: DWORD); stdcall;
var
peProcess: PVOID; { PEPROCESS }
fbDereference: Boolean;
us: UNICODE_STRING;
_as: ANSI_STRING;
begin
{ reserve DWORD on stack }
if
PsLookupProcessByProcessId(dwProcessId, peProcess) = STATUS_SUCCESS
then
begin
//pop
peProcess ; -> EPROCESS
fbDereference := True; { PsLookupProcessByProcessId references process object }
end
else
begin
{ PsLookupProcessByProcessId fails (on w2k only) with STATUS_INVALID_PARAMETER }
{
if
called
in
the very same process context. }
{ So
if
we are here it maight mean (on w2k) we are
in
process context being terminated. }
peProcess := IoGetCurrentProcess;
fbDereference := False; {IoGetCurrentProcess doesn't references process object }
end;
g_ProcessData.dwProcessId := dwProcessId;
g_ProcessData.bCreate := bCreate;
memset(@g_ProcessData.szProcessName, 0, SizeOf(IMAGE_FILE_PATH_LEN));
if
GetImageFilePath(peProcess, @us) = STATUS_SUCCESS
then
begin
//lea
eax, g_ProcessData.szProcessName
_as.Buffer := @g_ProcessData.szProcessName;
_as.MaximumLength := IMAGE_FILE_PATH_LEN;
_as._Length := 0;
RtlUnicodeStringToAnsiString(@_as, @us, False);
{ Free memory allocated by GetImageFilePath }
ExFreePool(us.Buffer);
end
else
begin
{ If we fail to get process's image
file
path }
{ just use only process name from EPROCESS. }
if
g_dwImageFileNameOffset <> 0
then
begin
memcpy(@g_ProcessData.szProcessName, PAnsiChar(DWORD(peProcess) + g_dwImageFileNameOffset), 16);
end;
end;
if
fbDereference
then
begin
ObfDereferenceObject(peProcess);
end;
{ Notify user-mode client. }
KeSetEvent(g_pkEventObject, 0, False);
end;
function
DispatchControl(p_DeviceObject:PDEVICE_OBJECT; p_Irp:PIRP): NTSTATUS; stdcall;
var
liDelayTime: LARGE_INTEGER;
pIoStkLoc: PIO_STACK_LOCATION;
UserHandle: Handle;
lpExEventObjectType: PPointer;
pObjectType: Pointer;
rtnCode: NTSTATUS;
begin
{ Initialize to failure. }
p_Irp^.IoStatus.Status := STATUS_UNSUCCESSFUL;
p_Irp^.IoStatus.Information := 0;
pIoStkLoc := IoGetCurrentIrpStackLocation(p_Irp);
if
pIoStkLoc^.Parameters.DeviceIoControl.IoControlCode = IOCTL_SET_NOTIFY
then
begin
if
pIoStkLoc^.Parameters.DeviceIoControl.InputBufferLength >= SizeOf(HANDLE)
then
begin
if
not g_fbNotifyRoutineSet
then
{ For sure }
begin
UserHandle := Handle(p_Irp^.AssociatedIrp.SystemBuffer^);
lpExEventObjectType := GetImportFunAddr(@ExEventObjectType);
pObjectType := PVOID(lpExEventObjectType^);
rtnCode := ObReferenceObjectByHandle(UserHandle, EVENT_MODIFY_STATE,
pObjectType, UserMode, @g_pkEventObject,
nil);
if
rtnCode = STATUS_SUCCESS
then
begin
{ If passed event handle is valid add a driver-supplied callback routine }
{ to a list of routines to be called whenever a process is created or deleted. }
rtnCode := PsSetCreateProcessNotifyRoutine(@ProcessNotifyRoutine, False);
p_Irp^.IoStatus.Status := rtnCode;
if
rtnCode = STATUS_SUCCESS
then
begin
g_fbNotifyRoutineSet := True;
DbgPrint(
'ProcessMon: Notification was set'
{ Make driver nonunloadable }
p_DeviceObject^.DriverObject^.DriverUnload := nil;
end
else
begin
DbgPrint(
'ProcessMon: Couldn'
't set notification'
end;
end
else
begin
p_Irp^.IoStatus.Status := rtnCode;
DbgPrint(
'ProcessMon: Couldn'
't reference user event object. Status: %08X'
end;
end;
end
else
begin
p_Irp^.IoStatus.Status := STATUS_BUFFER_TOO_SMALL;
end;
end
else
if
pIoStkLoc^.Parameters.DeviceIoControl.IoControlCode = IOCTL_REMOVE_NOTIFY
then
begin
{ Remove a driver-supplied callback routine from a list of routines }
{ to be called whenever a process is created or deleted. }
if
g_fbNotifyRoutineSet
then
begin
rtnCode := PsSetCreateProcessNotifyRoutine(@ProcessNotifyRoutine, True);
p_Irp^.IoStatus.Status := rtnCode;
if
rtnCode = STATUS_SUCCESS
then
begin
g_fbNotifyRoutineSet := False;
DbgPrint(
'ProcessMon: Notification was removed'
{ Just
for
sure. It's theoreticaly possible our ProcessNotifyRoutine is now being executed. }
{ So we wait
for
some small amount of
time
(~50 ms). }
liDelayTime.HighPart := liDelayTime.HighPart or -1;
liDelayTime.LowPart := ULONG(-1000000);
KeDelayExecutionThread(KernelMode, False, @liDelayTime);
{ Make driver unloadable }
p_DeviceObject^.DriverObject^.DriverUnload := @DriverUnload;
if
g_pkEventObject <> nil
then
begin
ObfDereferenceObject(g_pkEventObject);
g_pkEventObject := nil;
end;
end
else
begin
DbgPrint(
'ProcessMon: Couldn'
't remove notification'
end;
end;
end
else
if
pIoStkLoc^.Parameters.DeviceIoControl.IoControlCode = IOCTL_GET_PROCESS_DATA
then
begin
if
pIoStkLoc^.Parameters.DeviceIoControl.OutputBufferLength >= SizeOf(PROCESS_DATA)
then
begin
//mov
eax, [esi].AssociatedIrp.SystemBuffer
memcpy(p_Irp^.AssociatedIrp.SystemBuffer, @g_ProcessData, SizeOf(g_ProcessData));
p_Irp^.IoStatus.Status := STATUS_SUCCESS;
p_Irp^.IoStatus.Information := SizeOf(g_ProcessData);
end
else
begin
p_Irp^.IoStatus.Status := STATUS_BUFFER_TOO_SMALL;
end;
end
else
begin
p_Irp^.IoStatus.Status := STATUS_INVALID_DEVICE_REQUEST;
end;
{ After IoCompleteRequest returns, the IRP pointer }
{ is no longer valid and cannot safely be dereferenced. }
IofCompleteRequest(p_Irp, IO_NO_INCREMENT);
Result := p_Irp^.IoStatus.Status;
end;
function
GetImageFileNameOffset: DWORD;
var
iCnt: Integer;
iRtnVal: Integer;
pTmp: PAnsiChar;
begin
{ Finds EPROCESS.ImageFileName field offset }
{ W2K EPROCESS.ImageFileName = 01FCh }
{ WXP EPROCESS.ImageFileName = 0174h }
{ WNET EPROCESS.ImageFileName = 0154h }
{ Instead of hardcoding above offsets we just scan }
{ the EPROCESS structure of System process one page down. }
{ It's well-known trick. }
pTmp := PAnsiChar(IoGetCurrentProcess);
iCnt := 0;
iRtnVal := 0;
{ one page
more
than enough. }
while
iCnt < $1000
do
begin
{ Case insensitive compare. }
iRtnVal := _strnicmp(PAnsiChar(pTmp + iCnt), PAnsiChar(
'system'
), 6);
if
iRtnVal = 0
then
Break;
Inc(iCnt)
end;
if
iRtnVal = 0
then
begin
{ Found. }
Result := iCnt;
end
else
begin
{ Not found. }
Result := 0;
end;
end;
function
_DriverEntry(p_DriverObject: PDRIVER_OBJECT;
pusRegistryPath: PUNICODE_STRING): NTSTATUS; stdcall;
var
status: NTSTATUS;
pDeviceObject: PDEVICE_OBJECT;
begin
status := STATUS_DEVICE_CONFIGURATION_ERROR;
RtlInitUnicodeString(@g_usDeviceName,
'\Device\ProcessMon'
);
RtlInitUnicodeString(@g_usSymbolicLinkName,
'\DosDevices\ProcessMon'
);
if
IoCreateDevice(p_DriverObject, 0, @g_usDeviceName,
FILE_DEVICE_UNKNOWN, 0, True,
@pDeviceObject) = STATUS_SUCCESS
then
begin
if
IoCreateSymbolicLink(@g_usSymbolicLinkName, @g_usDeviceName) = STATUS_SUCCESS
then
begin
p_DriverObject^.MajorFunction[IRP_MJ_CREATE] := @DispatchCreateClose;
p_DriverObject^.MajorFunction[IRP_MJ_CLOSE] := @DispatchCreateClose;
p_DriverObject^.MajorFunction[IRP_MJ_DEVICE_CONTROL] := @DispatchControl;
p_DriverObject^.DriverUnload := @DriverUnload;
g_fbNotifyRoutineSet := False;
memset(@g_ProcessData, 0, SizeOf(g_ProcessData));
{ it can be not found and equal to 0, btw }
g_dwImageFileNameOffset := GetImageFileNameOffset;
status := STATUS_SUCCESS;
end
else
begin
IoDeleteDevice(pDeviceObject);
end;
end;
result := status;
end;
end.