x64驅動基礎教程 38
登錄檔回撥是一個比較監控登錄檔讀寫的回撥, 它的能量非常大, 一個回撥能實現在 SSDT 上 HOOK 十幾個 API 的效果。 部分遊戲保護還會在登錄檔回撥上做功夫,監控 service 鍵的子鍵,實現雙層攔截驅動載入(在映像回撥那裡還有一層)。 而在卡巴斯基等 HIPS 類軟體裡,則用來監控自啟動等鍵值。
登錄檔回撥在 XP 系統上貌似是一個數組, 但是從 WINDOWS 2003 開始,就變成了一個連結串列。 這個連結串列的頭稱為 CallbackListHead , 可在 CmUnRegisterCallback 中找到:
lkd> uf CmUnRegisterCallback nt!CmUnRegisterCallback: fffff800`01cba790 48894c2408 mov qword ptr [rsp+8],rcx fffff800`01cba795 53 push rbx fffff800`01cba796 56 push rsi fffff800`01cba797 57 push rdi fffff800`01cba798 4154 push r12 fffff800`01cba79a 4155 push r13 fffff800`01cba79c 4156 push r14 fffff800`01cba79e 4157 push r15 fffff800`01cba7a0 4883ec60 sub rsp,60h fffff800`01cba7a4 41bc0d0000c0 mov r12d,0C000000Dh fffff800`01cba7aa 4489a424b0000000 mov dword ptr [rsp+0B0h],r12d fffff800`01cba7b2 33db xor ebx,ebx fffff800`01cba7b4 48895c2448 mov qword ptr [rsp+48h],rbx fffff800`01cba7b9 33c0 xor eax,eax fffff800`01cba7bb 4889442450 mov qword ptr [rsp+50h],rax fffff800`01cba7c0 4889442458 mov qword ptr [rsp+58h],rax fffff800`01cba7c5 448d6b01 lea r13d,[rbx+1] fffff800`01cba7c9 458afd mov r15b,r13b fffff800`01cba7cc 4488ac24a8000000 mov byte ptr [rsp+0A8h],r13b fffff800`01cba7d4 440f20c7 mov rdi,cr8 fffff800`01cba7d8 450f22c5 mov cr8,r13 fffff800`01cba7dc f00fba351b6adcff00 lock btr dword ptr [nt!CallbackUnregisterLock (fffff800`01a81200)],0 fffff800`01cba7e5 720c jb nt!CmUnRegisterCallback+0x63 (fffff800`01cba7f3) //省略中間無關程式碼 nt!CmUnRegisterCallback+0xc6: fffff800`01cba856 4533c0 xor r8d,r8d fffff800`01cba859 488d542420 lea rdx,[rsp+20h] fffff800`01cba85e 488d0d6b69dcff lea rcx,[nt!CallbackListHead (fffff800`01a811d0)] fffff800`01cba865 e8a261e5ff call nt!CmListGetNextElement (fffff800`01b10a0c) fffff800`01cba86a 488bf8 mov rdi,rax fffff800`01cba86d 4889442428 mov qword ptr [rsp+28h],rax fffff800`01cba872 483bc3 cmp rax,rbx fffff800`01cba875 0f84b8000000 je nt!CmUnRegisterCallback+0x1a3 (fffff800`01cba933)
搜尋的過程跟尋找程序、執行緒、映像的陣列類似,根據 lea REG,XXX 來定位。 不過為了更加精準, 這次我採用兩次 lea REG,XXX 來定位:
ULONG64 FindCmpCallbackAfterXP() { ULONG64 uiAddress = 0; PUCHAR pCheckArea = NULL, i = 0, j = 0, StartAddress = 0, EndAddress = 0; ULONG64 dwCheckAddr = 0; UNICODE_STRING unstrFunc; UCHAR b1 = 0, b2 = 0, b3 = 0; ULONG templong = 0, QuadPart = 0xfffff800; RtlInitUnicodeString(&unstrFunc, L"CmUnRegisterCallback"); pCheckArea = (UCHAR*)MmGetSystemRoutineAddress(&unstrFunc); if (!pCheckArea) { KdPrint(("MmGetSystemRoutineAddress failed.")); return 0; } StartAddress = (PUCHAR)pCheckArea; EndAddress = (PUCHAR)pCheckArea + PAGE_SIZE; for (i = StartAddress; i < EndAddress; i++) { if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2)) { b1 = *i; b2 = *(i + 1); b3 = *(i + 2); if (b1 == 0x48 && b2 == 0x8d && b3 == 0x0d) //488d0d(lea rcx,) { j = i - 5; b1 = *j; b2 = *(j + 1); b3 = *(j + 2); if (b1 == 0x48 && b2 == 0x8d && b3 == 0x54) //488d54(lea rdx,) { memcpy(&templong, i + 3, 4); uiAddress = MakeLong64ByLong32(templong) + (ULONGLONG)i + 7; return uiAddress; } } } } return 0; }
定位完畢之後,就是列舉連結串列了。 登錄檔回撥是一個“結構體連結串列”, 類似於 EPROCESS ,它的定義如下:
typedef struct _CM_NOTIFY_ENTRY { LIST_ENTRY ListEntryHead; ULONG UnKnown1; ULONG UnKnown2; LARGE_INTEGER Cookie; ULONG64 Context; ULONG64 Function; }CM_NOTIFY_ENTRY, *PCM_NOTIFY_ENTRY;
我們只關心兩個值,一個是 Cookie , 一個是 Function 。 前者可以理解成登錄檔回撥的“控制代碼”(用 CmUnRegisterCallback 登出回撥傳入的就是這個 Cookie ), 後者是回撥函式的地址。程式碼如下:
ULONG CountCmpCallbackAfterXP(ULONG64* pPspLINotifyRoutine) { ULONG sum = 0; ULONG64 dwNotifyItemAddr; ULONG64* pNotifyFun; ULONG64* baseNotifyAddr; ULONG64 dwNotifyFun; LARGE_INTEGER cmpCookie; PLIST_ENTRY notifyList; PCM_NOTIFY_ENTRY notify; dwNotifyItemAddr = *pPspLINotifyRoutine; notifyList = (LIST_ENTRY *)dwNotifyItemAddr; do { notify = (CM_NOTIFY_ENTRY *)notifyList; if (MmIsAddressValid(notify)) { if (MmIsAddressValid((PVOID)(notify->Function)) && notify->Function > 0x8000000000000000) { DbgPrint("[CmCallback]Function=%p\tCookie=%p", (PVOID)(notify->Function), (PVOID)(notify->Cookie.QuadPart)); //notify->Function=(ULONG64)MyRegistryCallback; sum++; } } notifyList = notifyList->Flink; } while (notifyList != ((LIST_ENTRY*)(*pPspLINotifyRoutine))); return sum; }
執行效果類似於:
。 對付登錄檔回撥有三種方法(老三套):
1. 直接使用 CmUnRegisterCallback 把回撥登出;
2. 把連結串列中記錄的回撥地址修改為自定義的空函式的回撥地址;
3. 直接在目標回撥地址上寫一個 RET , 使其不執行任何程式碼就返回。
第三種方法沒有針對性,可以用於對付任何回撥函式。 DisableFunctionWithReturnValue 用來對付有返回值的回撥函式, DisableFunctionWithoutReturnValue 用於對付無返回值的回撥函式。
KIRQL WPOFFx64() { KIRQL irql = KeRaiseIrqlToDpcLevel(); UINT64 cr0 = __readcr0(); cr0 &= 0xfffffffffffeffff; __writecr0(cr0); _disable(); return irql; } void WPONx64(KIRQL irql) { UINT64 cr0 = __readcr0(); cr0 |= 0x10000; _enable(); __writecr0(cr0); KeLowerIrql(irql); } VOID DisableFunctionWithReturnValue(PVOID Address) { KIRQL irql; CHAR patchCode[] = "\x33\xC0\xC3"; //xor eax,eax + ret if (MmIsAddressValid(Address)) { irql = WPOFFx64(); memcpy(Address, patchCode, 3); WPONx64(irql); } } VOID DisableFunctionWithoutReturnValue(PVOID Address) { KIRQL irql; if (MmIsAddressValid(Address)) { irql = WPOFFx64(); RtlFillMemory(Address, 1, 0xC3); WPONx64(irql); } }