如下內容參考黑客防線2012合訂本316頁數組
1.首先要注意的問題函數
inline hook 不能截斷指令. 也就是說修改目標函數的指令實現跳轉到本身的函數裏面時, 不能截斷掉目標函數的指令.測試
由於在本身的函數裏面還要調用原來的函數,可是原來的函數若是被截斷那就沒辦法正常執行代碼編碼
2.反彙編引擎. spa
用來動態解析內存中指令, 這裏就是用來獲取所需字節數的最少修改指令數所佔的大小. 也就是防止出現指令截斷.代理
使用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); }
使用:code
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思路:blog
首先聲明全局變量來存儲 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); }