win 64 ring0 inline hook
阿新 • • 發佈:2017-10-03
防止 還要 全局 技術 png ret 寄存器 保存 ces
以下內容參考黑客防線2012合訂本316頁
1.首先要註意的問題
inline hook 不能截斷指令. 也就是說修改目標函數的指令實現跳轉到自己的函數裏面時, 不能截斷掉目標函數的指令.
因為在自己的函數裏面還要調用原來的函數,但是原來的函數如果被截斷那就沒辦法正常執行代碼
2.反匯編引擎.
用來動態解析內存中指令, 這裏就是用來獲取所需字節數的最少修改指令數所占的大小. 也就是防止出現指令截斷.
使用LDE64 (網上就有).
uchar szShellCode[12800]={...}; typedef int (*LDE_DISASM)(void *p, int dw); LDE_DISASM LDE;void LDE_init() { LDE=ExAllocatePool(NonPagedPool,12800); memcpy(LDE,szShellCode,12800); }
使用:
ULONG GetPatchSize(PUCHAR Address) { ULONG LenCount = 0, Len = 0; while (LenCount <= 14) //至少需要14字節 { Len = LDE(Address, 64); Address = Address + Len; LenCount = LenCount + Len; }return LenCount; }
關於inline hook思路:
首先聲明全局變量來存儲 A 代碼. A代碼就是原始的前n字節.
聲明全局變量存儲B代碼. 是A代碼+jmp C . 這個C是原始函數+sizeof(A)
A代碼用來unhook的 , B代碼用來從自己的函數跳回去原始函數繼續執行.
所以先獲取跳轉到自己函數的機器碼. D
這個機器碼是通過以下實現的:
這裏跨4G跳轉使用的方式是jmp qword ptr [rip], 對應的機器碼是ff2500000000 6個字節 當執行到這條指令時,假如這條指令地址是這樣 0000000000000000 jmp qword ptr [rip] 那麽又假如下一條指令這樣:0000000000000006 cccccccccccccccc (這裏的匯編碼為int3 int3 int3...int3 共8個) 那麽指令執行完jmp指令後,將跑到地址為cccccccccccccccc處的代碼執行,而不是執行int3 指令. 簡單來說就是把rip當成普通寄存器使用了. 因此至少需要14字節的數據來跳轉任意內存處. 為什麽是至少14字節呢? 因為指令不能截斷,當hook時不能直接只改掉前14字節,這樣可能會因為 某條指令橫跨第14字節,這樣hook就會將這條指令截斷. 因此需要通過反匯編引擎對字節碼進行 解析,直到解析的指令內容>=14字節.將這些指令內容大小作為修改的大小,多余的填充nop.
然後將前n字節保存到A.
設置好B
再將D寫入到原始函數前n字節. 這樣就實現inline hook.
在自己的函數裏面可以通過調用B來執行正確的原始函數.
unhook就很簡單, 將A寫回原始函數前n字節即可. 測試結果:
最後附上大佬的代碼:(有自己寫的一些註釋)
#include <ntddk.h> #define kmalloc(_s) ExAllocatePoolWithTag(NonPagedPool, _s, ‘SYSQ‘) #define kfree(_p) ExFreePool(_p) typedef NTSTATUS(__fastcall *PSLOOKUPPROCESSBYPROCESSID)(HANDLE ProcessId, PEPROCESS *Process); ULONG64 my_eprocess = 0; //待保護進程的eprocess ULONG pslp_patch_size = 0; //PsLookupProcessByProcessId被修改了N字節 PUCHAR pslp_head_n_byte = NULL; //PsLookupProcessByProcessId的前N字節數組 PVOID ori_pslp = NULL; //PsLookupProcessByProcessId的原函數 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); } //傳入:被HOOK函數地址,原始數據,補丁長度 VOID UnhookKernelApi(IN PVOID ApiAddress, IN PVOID OriCode, IN ULONG PatchSize) { KIRQL irql; irql = WPOFFx64(); memcpy(ApiAddress, OriCode, PatchSize); WPONx64(irql); } NTSTATUS Proxy_PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process) { NTSTATUS st; st = ((PSLOOKUPPROCESSBYPROCESSID)ori_pslp)(ProcessId, Process); if (NT_SUCCESS(st)) { if (*Process == (PEPROCESS)my_eprocess) { *Process = 0; st = STATUS_ACCESS_DENIED; } } return st; } void *GetFunctionAddr(PCWSTR FunctionName) { UNICODE_STRING UniCodeFunctionName; RtlInitUnicodeString(&UniCodeFunctionName, FunctionName); return MmGetSystemRoutineAddress(&UniCodeFunctionName); } /* 這裏跨4G跳轉使用的方式是jmp qword ptr [rip], 對應的機器碼是ff2500000000 6個字節 當執行到這條指令時,假如這條指令地址是這樣 0000000000000000 jmp qword ptr [rip] 那麽又假如下一條指令這樣: 0000000000000006 cccccccccccccccc (這裏的匯編碼為int3 int3 int3...int3 共8個) 那麽指令執行完jmp指令後,將跑到地址為cccccccccccccccc處的代碼執行,而不是執行int3 指令. 簡單來說就是把rip當成普通寄存器使用了. 因此至少需要14字節的數據來跳轉任意內存處. 為什麽是至少14字節呢? 因為指令不能截斷,當hook時不能直接只改掉前14字節,這樣可能會導致 某條指令橫跨第14字節,如果這樣hook就會將這條指令截斷. 因此需要通過反匯編引擎對字節碼進行 解析,直到解析的指令內容>=14字節.將這些指令內容大小作為修改的大小,多余的填充nop. */ ULONG GetPatchSize(PUCHAR Address) { ULONG LenCount = 0, Len = 0; while (LenCount <= 14) //至少需要14字節 { Len = LDE(Address, 64); Address = Address + Len; LenCount = LenCount + Len; } return LenCount; } //傳入:待HOOK函數地址,代理函數地址,接收跳回原始函數代碼內容的地址的指針,接收補丁長度的指針;返回:原來頭N字節的數據 PVOID HookKernelApi(IN PVOID ApiAddress, IN PVOID Proxy_ApiAddress, OUT PVOID *Original_ApiAddress, OUT ULONG *PatchSize) { //這裏面有2個是動態分配的,需要在程序卸載時釋放掉,是head_n_byte,ori_func //它們被賦值到返回值和參數Original_ApiAddress了 KIRQL irql; UINT64 tmpv; //一個是保存被hook函數前n字節碼,一個是保存代理函數調用原始函數用的代碼,不能直接調用原始函數,因為已經被改了. PVOID head_n_byte, ori_func; UCHAR jmp_code[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"; UCHAR jmp_code_orifunc[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"; //How many bytes shoule be patch *PatchSize = GetPatchSize((PUCHAR)ApiAddress); //step 1: Read current data head_n_byte = kmalloc(*PatchSize); irql = WPOFFx64(); memcpy(head_n_byte, ApiAddress, *PatchSize); WPONx64(irql); //step 2: Create ori function ori_func = kmalloc(*PatchSize + 14); //原始機器碼+跳轉機器碼 RtlFillMemory(ori_func, *PatchSize + 14, 0x90); tmpv = (ULONG64)ApiAddress + *PatchSize; //跳轉到沒被打補丁的那個字節 DbgPrint("ApiAddress is %p\n", ApiAddress); DbgBreakPoint(); memcpy(jmp_code_orifunc + 6, &tmpv, 8); memcpy((PUCHAR)ori_func, head_n_byte, *PatchSize); memcpy((PUCHAR)ori_func + *PatchSize, jmp_code_orifunc, 14); *Original_ApiAddress = ori_func; //step 3: fill jmp code tmpv = (UINT64)Proxy_ApiAddress; memcpy(jmp_code + 6, &tmpv, 8); //step 4: Fill NOP and hook irql = WPOFFx64(); RtlFillMemory(ApiAddress, *PatchSize, 0x90); memcpy(ApiAddress, jmp_code, 14); WPONx64(irql); //return ori code return head_n_byte; } VOID HookPsLookupProcessByProcessId() { //pslp_head_n_byte和ori_pslp 最後需要釋放掉 pslp_head_n_byte = HookKernelApi(GetFunctionAddr(L"PsLookupProcessByProcessId"), (PVOID)Proxy_PsLookupProcessByProcessId, &ori_pslp, &pslp_patch_size); } VOID UnhookPsLookupProcessByProcessId() { UnhookKernelApi(GetFunctionAddr(L"PsLookupProcessByProcessId"), pslp_head_n_byte, pslp_patch_size); }
win 64 ring0 inline hook