win 64 ring0 inline hook
Posted 心有猛虎,细嗅蔷薇
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了win 64 ring0 inline hook相关的知识,希望对你有一定的参考价值。
以下内容参考黑客防线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的主要内容,如果未能解决你的问题,请参考以下文章
LOL游戏程序中对一些函数的Hook记录(Win10 x64)