InlineHook
前言
IATHOOK局限性较大,当我们想HOOK一个普通函数,并不是API,或者IAT表里并没有这个API函数(有可能他自己LoadLibrary,自己加载的),那我们根本就从导入表中找不到这个函数,自然也就在IAT表中无法找到,InlineHook算是对IATHOOK一个升级版吧
大体思路
用JMP改变函数入口,JMP到我们自己的函数,然后又JMP回去执行刚刚的没执行完的函数。
过程无论怎么变,一定要让堆栈平衡和保留原来的寄存器,这是Hook是否成功的关键.
具体实现
创建钩子
1 DWORD SetInlineHook(LPBYTE HookAddr,LPVOID HookProc,DWORD num) //要挂钩子的地址,钩子函数(如何处理),要改多少个的硬编码 2 { 3 if (HookAddr == NULL || HookProc == NULL) 4 { 5 printf("地址填错了"); 6 return 0; 7 } 8 if (num < 5) 9 { 10 printf("HOOK不了"); 11 return 0; 12 } 13 //改变修改地址为可写属性 14 DWORD OldProtect = 0; 15 DWORD bret = VirtualProtect((LPBYTE)HookAddr,num, PAGE_EXECUTE_READWRITE,&OldProtect); 16 if (bret == 0) 17 { 18 printf("修改可写属性失败"); 19 return 0; 20 } 21 Buffer = malloc(num * sizeof(char)); 22 23 memcpy(Buffer, HookAddr, num); //存起来把原来的值 24 25 memset(HookAddr,0x90,num); //先全部nop 26 //计算跳到我们自己函数的硬编码,E9后面的值 = 要跳转的地址 - E9的地址 - 5 27 DWORD JmpAddr = (DWORD)HookProc - (DWORD)HookAddr - 5; 28 29 *(LPBYTE)HookAddr = 0xE9; 30 *(PDWORD)((LPBYTE)HookAddr + 1) = JmpAddr; 31 32 GlobleHookAddr = (DWORD)HookAddr; 33 RetGlobleHookAddr = (DWORD)HookAddr + num; //等会的返回地址 34 dw_ifHOOK = 1; 35 }
这里别忘了改属性,然后就是有个公式,JMP后面的值并不是我们真正想要去的地址
E9后面的值 = 要跳转的地址 - E9的地址 - 5,这里要算一下.
卸载钩子
1 DWORD UnInlineHook(DWORD num) 2 { 3 if (!dw_ifHOOK) 4 { 5 printf("还没hook呢"); 6 return 0; 7 } 8 memcpy((LPVOID)GlobleHookAddr, Buffer, num); 9 10 Buffer = NULL; 11 dw_ifHOOK = 0; 12 return 1; 13 }
这里把我们在创建钩子的时候定义的全局变量Buffer的值重新写回来就行了
钩子函数
1 extern "C" _declspec(naked) void HookProc() //裸函数,编译器不帮我们平衡堆栈 2 { 3 //先把现场保留了 4 _asm 5 { 6 pushad //保留寄存器 7 pushfd //保留标志寄存器 8 } 9 _asm 10 { 11 mov reg.EAX, eax 12 mov reg.EBX, ebx 13 mov reg.ECX, ecx 14 mov reg.EDX, edx 15 mov reg.EDI, edi 16 mov reg.ESI, esi 17 mov reg.ESP, esp 18 mov reg.EBP, ebp 19 } 20 _asm 21 { 22 mov eax, DWORD PTR ss : [esp + 0x28] 23 mov x, eax 24 mov eax, DWORD PTR ss : [esp + 0x2c] 25 mov y, eax 26 mov eax, DWORD PTR ss : [esp + 0x30] 27 mov z, eax 28 29 } 30 printf("EAX:%x EBX:%x ECX:%x EDX:%x EDI:%x ESI:%x ESP:%x EBP:%x \n", reg.EAX, reg.EBX, reg.ECX, reg.EDX, reg.EDI, reg.ESI, reg.ESP, reg.EBP); 31 32 printf("参数:%d %d %d\n", x, y, z); 33 34 _asm 35 { 36 popfd 37 popad 38 } 39 40 _asm 41 { 42 push ebp 43 mov ebp, esp 44 sub esp, 0C0h 45 } 46 47 _asm 48 { 49 jmp RetGlobleHookAddr; 50 } 51 }
上来先把寄存器的值保存下来,后面我们要还原现场.这里我先创建了一个结构体用于接收寄存器的值,等会方便打印
typedef struct _regeist { DWORD EAX; DWORD EBX; DWORD ECX; DWORD EDX; DWORD EBP; DWORD ESP; DWORD ESI; DWORD EDI; }regeist; regeist reg = { 0 };
然后第22~28行的值由于pushad和pushfd了,偏移不能是+4 +8 +c了,这里要算一下,40~45行,我们将原来没执行的代码执行一下,不然堆栈出问题了,最后跳转到原函数的下一个位置,继续执行原函数
被HOOK的函数
DWORD Test(int x, int y, int z) { return x + y + z; }
测试
1 DWORD TestInlineHook() 2 { 3 PAddr = (BYTE*)Test + 1; 4 PAddr += *(DWORD*)PAddr+ 4; 5 6 SetInlineHook((LPBYTE)Test, HookProc,9); 7 8 Test(1, 2, 3); 9 10 UnInlineHook(9); 11 12 Test(1, 2, 3); 13 return 0; 14 }
这里有一个小的细节,我们用函数名Test传参的话,传进去的这个参数的值并不是真正的函数地址,而是一个间接地址,间接地址里面的值是JMP到真正的函数地址。也就是我们平时看反汇编时,如果我们F11一个CALL我们会发现他先到一个地址,然后再F11,才会到真正的函数地址。用函数名传参的话得到的并不是真正的函数地址,但其实也是个间接地址嘛,JMP + 真正函数地址经过运算后的地址,我们还是用“E9后面的值 = 要跳转的地址 - E9的地址 - 5”这个公式算一下。
这里我找到一篇文章说明为什么有这种机制:https://blog.csdn.net/x_iya/article/details/13161937
测试结果
我调用了两次函数,但只有一次输出说明卸载也成功了
完整代码
// InlineHook.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <windows.h> //保留原来的硬编码 LPVOID Buffer; typedef struct _regeist { DWORD EAX; DWORD EBX; DWORD ECX; DWORD EDX; DWORD EBP; DWORD ESP; DWORD ESI; DWORD EDI; }regeist; regeist reg = { 0 }; DWORD x; DWORD y; DWORD z; DWORD GlobleHookAddr; DWORD RetGlobleHookAddr; DWORD dw_ifHOOK = 0; DWORD Test(int x, int y, int z); PBYTE PAddr; //typedef DWORD(*MyTest)(int x, int y, int z); //MyTest pAddr =Test; extern "C" _declspec(naked) void HookProc() //裸函数,编译器不帮我们平衡堆栈 { //先把现场保留了 _asm { pushad //保留寄存器 pushfd //保留标志寄存器 } _asm { mov reg.EAX, eax mov reg.EBX, ebx mov reg.ECX, ecx mov reg.EDX, edx mov reg.EDI, edi mov reg.ESI, esi mov reg.ESP, esp mov reg.EBP, ebp } _asm { mov eax, DWORD PTR ss : [esp + 0x28] mov x, eax mov eax, DWORD PTR ss : [esp + 0x2c] mov y, eax mov eax, DWORD PTR ss : [esp + 0x30] mov z, eax } printf("EAX:%x EBX:%x ECX:%x EDX:%x EDI:%x ESI:%x ESP:%x EBP:%x \n", reg.EAX, reg.EBX, reg.ECX, reg.EDX, reg.EDI, reg.ESI, reg.ESP, reg.EBP); printf("参数:%d %d %d\n", x, y, z); _asm { popfd popad } _asm { push ebp mov ebp, esp sub esp, 0C0h } _asm { jmp RetGlobleHookAddr; } } DWORD SetInlineHook(LPBYTE HookAddr,LPVOID HookProc,DWORD num) //要挂钩子的地址,钩子函数(如何处理),要改多少个的硬编码 { if (HookAddr == NULL || HookProc == NULL) { printf("地址填错了"); return 0; } if (num < 5) { printf("HOOK不了"); return 0; } //改变修改地址为可写属性 DWORD OldProtect = 0; DWORD bret = VirtualProtect((LPBYTE)HookAddr,num, PAGE_EXECUTE_READWRITE,&OldProtect); if (bret == 0) { printf("修改可写属性失败"); return 0; } Buffer = malloc(num * sizeof(char)); memcpy(Buffer, HookAddr, num); memset(HookAddr,0x90,num); //先全部nop //计算跳到我们自己函数的硬编码,这里用E8方便平衡堆栈,E8后面的值 = 要跳转的地址 - E8的地址 - 5 DWORD JmpAddr = (DWORD)HookProc - (DWORD)HookAddr - 5; *(LPBYTE)HookAddr = 0xE9; *(PDWORD)((LPBYTE)HookAddr + 1) = JmpAddr; GlobleHookAddr = (DWORD)HookAddr; RetGlobleHookAddr = (DWORD)HookAddr + num; dw_ifHOOK = 1; } DWORD UnInlineHook(DWORD num) { if (!dw_ifHOOK) { printf("还没hook呢"); return 0; } memcpy((LPVOID)GlobleHookAddr, Buffer, num); Buffer = NULL; dw_ifHOOK = 0; return 1; } DWORD Test(int x, int y, int z) { return x + y + z; } DWORD TestInlineHook() { PAddr = (BYTE*)Test + 1; PAddr += *(DWORD*)PAddr + 4; SetInlineHook((LPBYTE)PAddr, HookProc,9); Test(1, 2, 3); UnInlineHook(9); Test(1, 2, 3); return 0; } int main() { TestInlineHook(); //Test(1, 2, 3); return 1; }