用PsSetCreateProcessNotifyRoutine,PsSetCreateThreadNotifyRoutine來進行進程線程監控我想大家已經都非常熟練了.sinister在<<編寫進程/線程監視器>>一文中已經實現得很好了.前一段時間看到網上有人在研究監視遠線程的文章,比較有意思.就寫代碼玩一玩.這之中就出現了一些問題.比方說直接用sinister的代碼的話,是不能動態卸載的,因為他在安裝了進線程監視函數後沒有進行清除動作,造成在動態卸載時藍屏,BUGCHECK為0x000000ce,錯誤碼為:DRIVER_UNLOADED_WITHOUT_CANCELLING_PENDING_OPERATIONS.很顯然,在驅動退出後,一些進線程操作仍然在訪問原來的地址,造成出錯.在XP後,微軟給出了一個函數PsRemoveCreateThreadNotifyRoutine用來清除線程監視函數(清除進程監視的就是PsSetCreateProcessNotifyRoutine).我一直奇怪ICESWORD在2000中是怎麼做到進線程監視的.後來才發現,在運行icesword後釋放出一個detport.sys文件,然後一直在系統中存在著沒有卸載掉.只是把它隱藏了而已^_^.這不是個好消息,難道我為了測試一個驅動,測試一次就得重啟一次嗎?呵呵,肯定不是啊,所以想辦法搞定它.
我們來看一下進線程監視在底層是如何實現的,在win2000源代碼中先找到創建線程的函數實現:////////////////////////////////////////////////////////////////////////////////////////////////////////// \win2k\private\ntos\ps\create.h////////////////////////////////////////////////////////////////////////////////////////////////////////NTSTATUSPspCreateThread(......){...if (PspCreateProcessNotifyRoutineCount != 0) { //首先調用進程監控函數ULONG i;for (i=0; i<PSP_MAX_CREATE_PROCESS_NOTIFY; i ) {if (PspCreateProcessNotifyRoutine[i] != NULL) {(*PspCreateProcessNotifyRoutine[i])( Process->InheritedFromUniqueProcessId,Process->UniqueProcessId,TRUE);}}}
}......if (PspCreateThreadNotifyRoutineCount != 0) {ULONG i;
for (i=0; i<PSP_MAX_CREATE_THREAD_NOTIFY; i ) { //再調用線程監控函數if (PspCreateThreadNotifyRoutine[i] != NULL) {(*PspCreateThreadNotifyRoutine[i])( Thread->Cid.UniqueProcess,Thread->Cid.UniqueThread,TRUE);}}}......}
從上面可以看到,在每創建一個線程後會調用PspCreateProcessNotifyRoutine[i]地址指向的函數.而PsSetCreateThreadNotifyRoutine的作用就是將PspCreateThreadNotifyRoutine[i]數組設置值,該值就是監視函數的地址.
NTSTATUSPsSetCreateThreadNotifyRoutine(IN PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine){ULONG i;NTSTATUS Status;
Status = STATUS_INSUFFICIENT_RESOURCES;for (i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i = 1) {if (PspCreateThreadNotifyRoutine[i] == NULL) {PspCreateThreadNotifyRoutine[i] = NotifyRoutine;PspCreateThreadNotifyRoutineCount = 1;Status = STATUS_SUCCESS;break;}}
return Status;}上面的一些結構如下:////////////////////////////////////////////////////////////////////////////////////////////////////////// \win2k\private\ntos\ps\psp.h////////////////////////////////////////////////////////////////////////////////////////////////////////#define PSP_MAX_CREATE_THREAD_NOTIFY 8 //最大監視數目
ULONG PspCreateThreadNotifyRoutineCount; //用來記數PCREATE_THREAD_NOTIFY_ROUTINE PspCreateThreadNotifyRoutine[ PSP_MAX_CREATE_THREAD_NOTIFY ]; //函數地址數組
而PCREATE_THREAD_NOTIFY_ROUTINE定義如下:typedefVOID(*PCREATE_THREAD_NOTIFY_ROUTINE)(IN HANDLE ProcessId,IN HANDLE ThreadId,IN BOOLEAN Create);
相應的,進程的結構也是一樣的.通過上面,我們可以看到,只要我們找出該函數數組地址,在我們退出驅動時先將其全部清零,清零的大小為PSP_MAX_CREATE_THREAD_NOTIFY,這樣的話下一次的進線程操作就不會調用這個函數指針了.也就讓系統回到正常,我們再通過PsSetCreateProcessNotifyRoutine來驗證一下:
NTSTATUSPsSetCreateProcessNotifyRoutine(IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,IN BOOLEAN Remove){ULONG i;
for (i=0; i < PSP_MAX_CREATE_PROCESS_NOTIFY; i ) {if (Remove) { if (PspCreateProcessNotifyRoutine[i] == NotifyRoutine) { //清除時就是簡單的賦植操作PspCreateProcessNotifyRoutine[i] = NULL;PspCreateProcessNotifyRoutineCount -= 1; //將計數器減一return STATUS_SUCCESS;}} else {if (PspCreateProcessNotifyRoutine[i] == NULL) { //設置時也是簡單的賦值操作PspCreateProcessNotifyRoutine[i] = NotifyRoutine;PspCreateProcessNotifyRoutineCount = 1; //將計數器加一return STATUS_SUCCESS;}}}
return Remove ? STATUS_PROCEDURE_NOT_FOUND : STATUS_INVALID_PARAMETER;}
好了,方法已經知道了,只要找出地址,我們就能夠"全身而退"了.看一下windows2003下面的PsRemoveCreateThreadNotifyRoutine實現: lkd> u PsRemoveCreateThreadNotifyRoutine l 20nt!PsRemoveCreateThreadNotifyRoutine:80651d7b 53 push ebx80651d7c 56 push esi80651d7d 57 push edi80651d7e 33db xor ebx,ebx80651d80 bf400f5780 mov edi,0x80570f40 //起始地址80651d85 57 push edi80651d86 e8a7500100 call nt!ExWaitForRundownProtectionRelease 0x5cf (80666e32)80651d8b 8bf0 mov esi,eax80651d8d 85f6 test esi,esi80651d8f 7420 jz nt!PsRemoveCreateThreadNotifyRoutine 0x36 (80651db1)80651d91 56 push esi80651d92 e8ba1bffff call nt!IoReportTargetDeviceChange 0x7aa0 (80643951)80651d97 3b442410 cmp eax,[esp 0x10]80651d9b 750d jnz nt!PsRemoveCreateThreadNotifyRoutine 0x2f (80651daa)80651d9d 56 push esi80651d9e 6a00 push 0x080651da0 57 push edi80651da1 e8c54f0100 call nt!ExWaitForRundownProtectionRelease 0x508 (80666d6b)80651da6 84c0 test al,al80651da8 751b jnz nt!PsRemoveCreateThreadNotifyRoutine 0x4a (80651dc5)80651daa 56 push esi80651dab 57 push edi80651dac e892510100 call nt!ExWaitForRundownProtectionRelease 0x6e0 (80666f43)80651db1 43 inc ebx80651db2 83c704 add edi,0x480651db5 83fb08 cmp ebx,0x8 //看是否到了最大數(8)80651db8 72cb jb nt!PsRemoveCreateThreadNotifyRoutine 0xa (80651d85)80651dba b87a0000c0 mov eax,0xc000007a80651dbf 5f pop edi80651dc0 5e pop esi80651dc1 5b pop ebx80651dc2 c20400 ret 0x4
lkd> dd 0x80570f40 //設置了監視函數後80570f40 e316e557 00000000 00000000 00000000.............................
lkd> dd 0x80570f40 //清除了監視函數後80570f40 00000000 00000000 00000000 00000000
哈哈.下面是實現代碼,代碼中實現了進線的的監視,並且實現了遠線程的監視:
Drivers.c/////////////////////////////////////////////////////////////////////////////////////////////////////////// // Made By ZwelL
#include "ntddk.h"#include "windef.h"#include "define.h"
#define SYSNAME "System"#define VERSIONLEN 100
const WCHAR devLink[] = L"\\??\\MyEvent";const WCHAR devName[] = L"\\Device\\MyEvent";UNICODE_STRING devNameUnicd;UNICODE_STRING devLinkUnicd; PVOID gpEventObject = NULL; // 與應用程序通信的 Event 對象ULONG ProcessNameOffset =0;PVOID outBuf[255];BOOL g_bMainThread; ULONG g_dwParentId;CHECKLIST CheckList;ULONG BuildNumber; //系統版本號 ULONG SYSTEMID; //System進程的IDPWCHAR Version[VERSIONLEN];
NTSTATUS PsLookupProcessByProcessId(IN ULONG ulProcId, OUT PEPROCESS * pEProcess);
ULONG GetProcessNameOffset(){PEPROCESS curproc;int i;
curproc = PsGetCurrentProcess();
for( i = 0; i < 3*PAGE_SIZE; i ) {if( !strncmp( SYSNAME, (PCHAR) curproc i, strlen(SYSNAME) )) {return i;}}
return 0;}
NTSTATUS GetRegValue(PCWSTR RegPath,PCWSTR ValueName,PWCHAR Value){int ReturnValue = 0;NTSTATUS Status;OBJECT_ATTRIBUTES ObjectAttributes;HANDLE KeyHandle;PKEY_VALUE_PARTIAL_INFORMATION valueInfoP;ULONG valueInfoLength,returnLength;UNICODE_STRING UnicodeRegPath;UNICODE_STRING UnicodeValueName;
RtlInitUnicodeString(&UnicodeRegPath, RegPath);RtlInitUnicodeString(&UnicodeValueName, ValueName);
InitializeObjectAttributes(&ObjectAttributes,&UnicodeRegPath,OBJ_CASE_INSENSITIVE, // FlagsNULL, // Root directoryNULL); // Security descriptor
Status = ZwOpenKey(&KeyHandle,KEY_ALL_ACCESS,&ObjectAttributes);if (Status != STATUS_SUCCESS){DbgPrint("ZwOpenKey Wrong\n");return 0;}
valueInfoLength = sizeof(KEY_VALUE_PARTIAL_INFORMATION) VERSIONLEN;valueInfoP = (PKEY_VALUE_P