const
IOCTL_SET_NOTIFY = (FILE_DEVICE_UNKNOWN shl 16) or (FILE_WRITE_ACCESS shl 14) or ($800 shl 2) or METHOD_BUFFERED;
IOCTL_REMOVE_NOTIFY = (FILE_DEVICE_UNKNOWN shl 16) or (0 shl 14) or ($801 shl 2) or 0;
IOCTL_GET_PROCESS_DATA = (FILE_DEVICE_UNKNOWN shl 16) or (FILE_READ_ACCESS shl 14) or ($802 shl 2) or METHOD_BUFFERED;
IMAGE_FILE_PATH_LEN = 512;
type
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
PROCESS_DATA = packed record
bCreate: DWORD;
dwProcessId: DWORD;
{ full process's image file path }
szProcessName: array[0..IMAGE_FILE_PATH_LEN - 1] of AnsiChar;
end;
unit main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, WinSvc, common;
type
TForm1 = class(TForm)
lvProcessInfo: TListView;
procedure FormActivate(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
g_hEvent: THandle;
g_fbExitNow: Boolean;
g_hDevice: THandle;
implementation
uses
GetData;
var
g_hSCManager: THandle;
g_hService: THandle;
tgd: TGetData;
{$R *.dfm}
procedure TForm1.FormActivate(Sender: TObject);
var
acModulePath: string;
lpTemp: PChar;
dwBytesReturned: DWORD;
begin
g_hSCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if g_hSCManager <> 0 then
begin
acModulePath := GetCurrentDir + '\' + ExtractFileName('ProcessMon.sys');
g_hService := CreateService(g_hSCManager, 'ProcessMon',
'Process creation/destruction monitor',
SERVICE_START or SERVICE_STOP or _DELETE,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_IGNORE,
PChar(acModulePath),
nil, nil, nil, nil, nil);
if g_hService <> 0 then
begin
if StartService(g_hService, 0, lpTemp) then
begin
g_hDevice := CreateFile('\\.\ProcessMon', GENERIC_READ or GENERIC_WRITE,
0, nil, OPEN_EXISTING, 0, 0);
if g_hDevice <> INVALID_HANDLE_VALUE then
begin
{ No need it to be registered anymore }
DeleteService(g_hService);
{ Create unnamed auto-reset event to be signalled when there is data to read. }
g_hEvent := CreateEvent(nil, False, false, nil);
{ Create thread to wait event signalled. }
tgd := TGetData.Create(False);
if not DeviceIoControl(g_hDevice, IOCTL_SET_NOTIFY,
@g_hEvent, SizeOf(g_hEvent), nil, 0,
dwBytesReturned, nil) then
begin
ShowMessage('无法设置通知!');
end;
end else
begin
ShowMessage('无法打开设备!');
end;
end else
begin
ShowMessage('无法启动驱动!');
end;
end else
begin
ShowMessage('无法注册驱动!');
end;
end else
begin
ShowMessage('无法连接到SCM!');
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
dwBytesReturned: DWORD;
_ss: SERVICE_STATUS;
begin
DeviceIoControl(g_hDevice, IOCTL_REMOVE_NOTIFY,
nil, 0, nil, 0, dwBytesReturned, nil);
g_fbExitNow := true; { If exception has occured not in loop thread it should exit now. }
SetEvent(g_hEvent);
Sleep(100);
CloseHandle(g_hEvent);
CloseHandle(g_hDevice);
ControlService(g_hService, SERVICE_CONTROL_STOP, _ss);
DeleteService(g_hService);
CloseServiceHandle(g_hService);
CloseServiceHandle(g_hSCManager);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
if(MessageDlg('由于条件所限,本驱动仅在Windows XP sp3上做过测试,是否继续?',
mtWarning, [mbYes, mbNo], 0, mbYes) = mrNo) then
begin
Application.Terminate;
end;
end;
end.
if not DeviceIoControl(g_hDevice, IOCTL_SET_NOTIFY,
@g_hEvent, SizeOf(g_hEvent), nil, 0,
dwBytesReturned, nil) then
unit GetData;
interface
uses
Windows, WinSvc, Classes, common, ComCtrls, SysUtils, Dialogs;
type
TGetData = class(TThread)
private
{ Private declarations }
ProcessData: PROCESS_DATA;
procedure FillProcessInfo;
protected
constructor Create(CreateSuspended: Boolean);
procedure Execute; override;
end;
implementation
{
Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure TGetData.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end;
or
Synchronize(
procedure
begin
Form1.Caption := 'Updated in thread via an anonymous method'
end
)
);
where an anonymous method is passed.
Similarly, the developer can call the Queue method with similar parameters as
above, instead passing another TThread class as the first parameter, putting
the calling thread in a queue with the other thread.
}
{ TGetData }
uses
main;
constructor TGetData.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
Priority := tpHighest;
end;
procedure TGetData.FillProcessInfo;
var
buffer: array[0..1023] of AnsiChar;
rtnVal: DWORD;
pTmp: PAnsiChar;
tlItems: TListItem;
iItemCnt: Integer;
begin
{ The path can be it the short form. Convert it to long. }
{ If no long path is found or path is in long form, GetLongPathName }
{ simply returns the specified path. }
FillChar(buffer, SizeOf(buffer), 0);
rtnVal := GetLongPathName(@processData.szProcessName, @buffer, SizeOf(buffer));
if (rtnVal = 0) or (rtnVal >= SizeOf(buffer)) then
begin
{ 1024 bytes was not enough. Just display whatever we've got from the driver. }
{ I want to keep the things simple. But you'd better to allocate more memory }
{ and call GetLongPathName again and again until the buffer size will }
{ satisfy the need. }
pTmp := @processData.szProcessName;
end else
pTmp := @buffer;
tlItems := Form1.lvProcessInfo.Items.Add;
tlItems.Caption := string(pTmp);
tlItems.SubItems.Add(Format('%8.8X', [processData.dwProcessId]));
if ProcessData.bCreate <> 0 then
tlItems.SubItems.Add('Created')
else
tlItems.SubItems.Add('Destroyed');
iItemCnt := Form1.lvProcessInfo.Items.Count;
Form1.lvProcessInfo.Items[iItemCnt - 1].MakeVisible(True);
end;
procedure TGetData.Execute;
var
hThread: THandle;
dwBytesReturned: DWORD;
begin
{ Place thread code here }
while True do
begin
if WaitForSingleObject(g_hEvent, INFINITE) <> WAIT_FAILED then
begin
if g_fbExitNow then
Break;
if DeviceIoControl(g_hDevice, IOCTL_GET_PROCESS_DATA, nil, 0,
@ProcessData, SizeOf(ProcessData),
dwBytesReturned, nil) then
begin
Synchronize(FillProcessInfo);
end;
end else
begin
ShowMessage('Wait for event failed. Thread now exits. Restart application.');
Break;
end;
Sleep(1);
end;
end;
end.
constructor TGetData.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
Priority := tpHighest;
end;
while True do
begin
if WaitForSingleObject(g_hEvent, INFINITE) <> WAIT_FAILED then
begin
if g_fbExitNow then
Break;
if DeviceIoControl(g_hDevice, IOCTL_GET_PROCESS_DATA, nil, 0,
@ProcessData, SizeOf(ProcessData),
dwBytesReturned, nil) then
begin
Synchronize(FillProcessInfo);
end;
Var
buffer: array[0..1023] of AnsiChar;
… …
FillChar(buffer, SizeOf(buffer), 0);
rtnVal := GetLongPathName(@processData.szProcessName, @buffer, SizeOf(buffer));
if (rtnVal = 0) or (rtnVal >= SizeOf(buffer)) then
begin
{ 1024 bytes was not enough. Just display whatever we've got from the driver. }
{ I want to keep the things simple. But you'd better to allocate more memory }
{ and call GetLongPathName again and again until the buffer size will }
{ satisfy the need. }
pTmp := @processData.szProcessName;
end else
pTmp := @buffer;
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'#13#10);
{ Make driver nonunloadable }
p_DeviceObject^.DriverObject^.DriverUnload := nil;
end else
begin
DbgPrint('ProcessMon: Couldn''t set notification'#13#10);
end;
end else
begin
p_Irp^.IoStatus.Status := rtnCode;
DbgPrint('ProcessMon: Couldn''t reference user event object. Status: %08X'#13#10, rtnCode);
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'#13#10);
{ 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'#13#10);
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.
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;