测试代码与环境
环境:virtual studio 2022
编译目标:x86
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <Windows.h>
void printHello() { std::cout << "Hello World" << std::endl; }
void hookedHello() { std::cout << "Bye World" << std::endl; }
void installHook() { unsigned char jmp_code[5] = { 0 }; jmp_code[0] = 0xE9; int offset = (int)hookedHello - ((int)printHello + 5); *(int*)&jmp_code[1] = offset; DWORD oldProtect = 0; VirtualProtect(printHello, 4096, PAGE_EXECUTE_READWRITE, &oldProtect); memcpy(printHello, jmp_code, 5); }
int main() { installHook(); printHello(); return 0; }
|
编码思路
按照我从字面上的理解,原本的程序执行是一条拉直的线,而Hook
的中文是钩子,通过钩子就可以将程序的线给拉歪,使其部分发生变动,但不影响其整体走向。
而Inline Hook
(内联钩子)则是一种用于修改或监视程序执行流程的技术。它通过修改目标函数的二进制代码,将目标函数的一部分或全部替换为跳转指令,从而实现在目标函数执行之前或之后注入自定义的代码。内联钩子通常用于实现函数的跟踪、监控、调试、破解等场景。
内联钩子的实现原理一般分为三个步骤:
- 定位目标函数:通过符号、地址、函数名等方式定位目标函数的地址。
- 修改目标函数的二进制代码:将目标函数的指令序列中的一部分或全部替换为跳转指令(通常是
jmp
或 call
指令),跳转到钩子函数中执行。 - 编写钩子函数:编写用于替换目标函数指令的自定义代码,通常会在其中执行一些操作,然后再跳转回原始的目标函数继续执行。
代码解析
可以看到,虽然我们调用的是printHello()
,但是因为我们的钩子,程序实际执行时调用了hookedHello()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void installHook() { unsigned char jmp_code[5] = { 0 }; jmp_code[0] = 0xE9; int offset = (int)hookedHello - ((int)printHello + 5); *(int*)&jmp_code[1] = offset; DWORD oldProtect = 0; VirtualProtect(printHello, 4096, PAGE_EXECUTE_READWRITE, &oldProtect); memcpy(printHello, jmp_code, 5); }
|
这样,当程序执行到 printHello
函数时,实际上会跳转到 hookedHello
函数执行,从而实现了对 printHello
函数的hook
。
将断点断在内存拷贝前
步过
可以看到,相同地址的汇编指令发生了变化。HOOK成功。
接下来HOOK一个Windows API,时间关系今天先写这些。