Inline hook
Inline hook(内联钩子)是一种再程序运行时修改函数执行流程
的技术。通过修改函数原始代码 讲目标函数的执行路径重定向到自定义的代码段,从而实现对目标函数的拦截和修改。
对一个函数hook的方法:
- 从哪来到哪去
- 如何实现跳转
E9 + 四字节相对地址(E9是jmp的值)(相当于一共5字节)
修改时,如果指令长度大于5,则多余的值会和下一条代码结合为新代码,所以防止崩溃可以写成nop(90)
这个相对地址不算这条指令本身
- 如何修改代码
当已经有一个我们想要hook的函数时,需要先获得它的传入参数,并令hook函数的参数与想要hook的函数的参数类型,个数相同,以保证堆栈平衡
为了在运行时修改代码需要virtualprotect函数(windows下)来修改某处的访问权限
注:实验环境为x86,因为在x64下相对地址的值大于4字节导致不能正常跳转
那么尝试修改并hook一个函数:
0x1
1 2 3 4 5 6
| int main() { hooker(MessageBoxW, mybox, 5); MessageBoxW(0, L"Hello", 0, 0); return 0; }
|
main函数先调用hooker函数,修改messagebox的内容,然后执行被修改的messagebox函数
那么hooker函数就要传入messagebox函数的地址,和我们想要修改成的函数的地址,这里的长度可以是固定的,可以不写5
1 2 3 4 5 6 7 8 9 10
| void hooker(void* src, void* dst, int len) { DWORD old; VirtualProtect(src, len, 0x40, &old); memcpy(back, src, len); *(BYTE*)src = 0xE9; uintptr_t ra = (uintptr_t)dst - (uintptr_t)src - 5; *(DWORD*)((BYTE*)src + 1) = ra; VirtualProtect(src, len, old, &old);
}
|
在函数外面创建一个备份BYTE back[5];用来保存原始数据,然后用VirtualProtect修改函数前5个字节变成可写属性(0x40),然后备份原始属性到old并修改,核心公式是:
1 2
| *(BYTE*)src = 0xE9; uintptr_t ra = (uintptr_t)dst - (uintptr_t)src - 5;
|
然后改回去
最后就是实现自己的函数,一定要注意函数的传入参数必须要和原函数相同
1 2 3 4 5 6 7 8 9 10 11
| int WINAPI mybox(_In_opt_ HWND hWnd, _In_opt_ LPCWSTR lpText, _In_opt_ LPCWSTR lpCaption, _In_ UINT uType) { lpText = L"hooked"; unhook(MessageBoxW, back, 5); MessageBoxW(hWnd, lpText, lpCaption, uType); hooker(MessageBoxW, mybox, 5); return 0; }
|
这里通过去官网直接抄传入参数就可以直接得到参数名
同时注意要先解除钩子再调用原函数不然会陷入循环递归
1 2 3 4 5 6
| void unhook(void* src, void* back, int len) { DWORD old; VirtualProtect(src, len, 0x40, &old); memcpy(src, back, 5); VirtualProtect(src, len, old, &old); }
|
在调用完目标函数后,还要再次钩住函数,等待下一次调用
0x2
由于刚刚的hook函数,每次必须要先hook,然后再unhook,十分麻烦。同时,每次调用messagebox的效果都是一样的,如果想要实现显示其它东西,需要另一种方法。
对于第一个问题:如果不对hook过的函数unhook,那么就会陷入死循环,无限调用自己,如果现在有一个和messagebox的函数效果相同的函数,在hook后,不调用messagebox,而是调用这个函数,就不会内存溢出
对于第二个问题:如果有一个和messagebox函数相同的函数,那么直接调用那个函数并修改参数就可以显示其它东西。
因此,只要能构造出一个和我们想hook的函数功能相同的函数就可以解决上面两个问题。
那么先构造trampoline函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void* trampoline(void* src, void* dst, int len) { BYTE* boxin = (BYTE*)VirtualAlloc(0, len + 5, 0x00001000, 0x40); memcpy(boxin, src, len); *(boxin + len) = 0xe9; *(DWORD*)(boxin + len + 1) = (BYTE*)src - boxin - 5; DWORD old; VirtualProtect(src, len, 0x40, &old); *(BYTE*)src = 0xE9; uintptr_t ra = (uintptr_t)dst - (uintptr_t)src - 5; *(DWORD*)((BYTE*)src + 1) = ra; VirtualProtect(src, len, old, &old); return boxin; }
|
前4句都是在创建功能相同函数,直接申请内存把messagebox前5个字节复制过来,然后jmp到messagebox里(偷懒)
第5句到10句的hooker和之前的hooker一样
1 2 3 4 5 6 7 8
| void hooker(void* src, void* dst, int len) { DWORD old; VirtualProtect(src, len, 0x40, &old); *(BYTE*)src = 0xE9; uintptr_t ra = (uintptr_t)dst - (uintptr_t)src - 5; *(DWORD*)((BYTE*)src + 1) = ra; VirtualProtect(src, len, old, &old); }
|
最后一句return是为了之后使用不被hook的函数
然后先把相同功能函数的调用写出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| using PFUN = int (WINAPI*)( _In_opt_ HWND hWnd, _In_opt_ LPCWSTR lpText, _In_opt_ LPCWSTR lpCaption, _In_ UINT uType); PFUN pfun = nullptr; int main() { pfun = (PFUN)trampoline(MessageBoxW, mybox, 5); MessageBoxW(0, L"Hello", 0, 0); MessageBoxW(0, L"Hello", 0, 0); MessageBoxW(0, L"Hello", 0, 0); pfun(0, L"123123", 0, 0); return 0; }
|
最后添上hook函数
1 2 3 4 5 6 7 8 9
| int WINAPI mybox(_In_opt_ HWND hWnd, _In_opt_ LPCWSTR lpText, _In_opt_ LPCWSTR lpCaption, _In_ UINT uType) { lpText = L"hooked"; pfun(hWnd, lpText, lpCaption, uType); return 0; }
|
这样调用时,会先显示3个hook,在最后显示123123.
完