# inlinehook **Repository Path**: cutecuteyu/inlinehook ## Basic Information - **Project Name**: inlinehook - **Description**: 使用C++的inlinehook技术示例 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-08-23 - **Last Updated**: 2026-03-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Inline Hook 一个用于演示 Windows 内联钩子技术的 C++ 项目。 ## 项目结构 ``` inlinehook/ ├── README.md # 中文说明文档 ├── README.en.md # English documentation ├── LICENSE # MIT 许可证 └── Project1/ ├── 源.cpp # 主程序入口 ├── 手动测试.cpp # 测试程序 ├── Project1.sln # 解决方案文件 ├── Project1.vcxproj # 项目文件 └── Project2/ ├── 代码实现.cpp # Inline Hook 核心实现 └── Project2.vcxproj ``` ## 介绍 本项目演示了 **Inline Hook(内联钩子)** 技术的基本原理和实现方法。Inline Hook 是一种通过修改函数入口处指令来拦截和重定向函数调用的技术。 ### 核心功能 - 拦截 Windows API 函数调用 - 在目标函数执行前/后注入自定义逻辑 - 完整恢复原始函数功能 --- ## 底层原理详解 ### 1. 什么是 Inline Hook? Inline Hook(内联钩子)是一种运行时补丁技术,通过直接修改目标函数入口处的机器码,将程序执行流重定向到我们自定义的函数中。 与传统的 Windows 钩子(如 SetWindowsHookEx)不同,Inline Hook 直接修改内存中的函数代码,因此可以拦截任何函数调用,无论是系统 API 还是自定义函数。 ### 2. x86 JMP 指令详解 在 x86 架构中,**JMP(Jump)指令**用于无条件跳转到指定地址。相对跳转(Relative Jump)的机器码格式为: ``` [0xE9] [4字节偏移量] ``` - `0xE9`:JMP 指令的操作码(Opcode) - `4字节偏移量**:目标地址相对于下一条指令的偏移(32位有符号整数) **跳转偏移量计算公式**: ``` 偏移量 = 目标地址 - (当前指令地址 + 5) ``` 其中 +5 是因为 JMP 指令本身占用 5 字节(1 字节操作码 + 4 字节偏移量)。 例如: - 假设 `CreateFileA` 地址 = 0x7FFA1234 - 假设 `MY_CreateFileA` 地址 = 0x00401000 - 偏移量 = 0x00401000 - (0x7FFA1234 + 5) = 负数(向低地址跳转) ### 3. 代码修改原理 以本项目为例,Hook `CreateFileA` 函数的完整步骤: ```cpp // 目标函数地址 char* target = (char*)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "CreateFileA"); ``` #### 步骤 1:修改内存保护 代码段(.text 节)通常是只读的(PAGE_EXECUTE_READ),不能直接写入。需要使用 `VirtualProtect` 修改内存属性: ```cpp DWORD oldProtect; VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &oldProtect); ``` 参数说明: - `target`:目标函数地址 - `5`:要修改的字节数(5字节 = 1字节JMP操作码 + 4字节偏移量) - `PAGE_EXECUTE_READWRITE`:允许读取、写入、执行 - `&oldProtect`:输出参数,保存原始保护属性以便后续恢复 #### 步骤 2:备份原始指令 ```cpp char backups_asm[5]; memcpy(backups_asm, target, 5); ``` 保存原函数入口的 5 字节,这5字节可能是: - 函数的前几条指令 - 或者是指令的一部分(如果指令跨5字节边界) 备份是为了之后能够恢复原始函数功能。 #### 步骤 3:写入 JMP 指令 ```cpp unsigned char jmp_0xe9 = 0xE9; unsigned int jmp_addr = detour - (target + 5); memcpy(target, &jmp_0xe9, 1); // 写入操作码 0xE9 memcpy(target + 1, &jmp_addr, 4); // 写入偏移量(4字节) ``` 这会将原函数入口处的指令替换为 `JMP 目标地址`,从而跳转到我们的 Hook 函数。 修改后的内存布局: ``` 原函数入口 (target): ┌─────────┬──────────────┐ │ 0xE9 │ 4字节偏移量 │ ├─────────┴──────────────┤ │ JMP 指令 (5字节) │ └───────────────────────┘ ``` ### 4. Hook 函数的实现原理 在 Hook 函数中,我们需要: 1. **执行自定义逻辑**:打印参数、记录日志、修改参数等 2. **恢复原始函数**:把备份的5字节写回,让原函数能正常执行 3. **调用原函数**:执行原始功能 4. **可选:重新安装 Hook**:如果需要持续拦截 ```cpp HANDLE __stdcall MY_CreateFileA( LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { DWORD oldProtect; // 1. 执行自定义逻辑 printf("MY_CreateFileA: %s \n", lpFileName); MessageBoxA(NULL, "aaaaa", "bbbbb", MB_OK); // 2. 恢复原始指令(5字节) VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &oldProtect); memcpy(target, backups_asm, 5); VirtualProtect(target, 5, oldProtect, &oldProtect); // 3. 调用原函数 return CreateFileA( lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } ``` **为什么要恢复原始指令?** - 因为 JMP 指令已经覆盖了原函数入口的前5字节 - 如果不恢复,原函数就无法正常执行 - 恢复后调用原函数,完成后再重新写入 JMP 指令(如果需要持续Hook) ### 5. 调用约定 (__stdcall) ```cpp HANDLE __stdcall MY_CreateFileA(...) ``` `__stdcall` 是 Windows API 标准的调用约定: - **参数传递**:参数从右向左压入堆栈 - **栈清理**:由被调用者(Callee)清理堆栈 - **名称修饰**:函数名称前加下划线,后加@和参数总字节数 这确保了我们自定义函数的参数布局与原函数一致,调用者能够正确传递和清理参数。 --- ## 代码详细讲解 ### 完整代码分析 #### 1. 获取目标函数地址 ```cpp char backups_asm[5]; char* target = (char*)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "CreateFileA"); ``` - `GetModuleHandleA("Kernel32.dll")`:获取 Kernel32.dll 的模块句柄 - `GetProcAddress(...)`:获取指定导出函数的地址 - `CreateFileA`:ANSI 版本的 CreateFile(注意不是 Unicode 版本的 CreateFileW) #### 2. 第一次调用(Hook 前) ```cpp printf("第一次调用CreateFileA \n"); hFile = CreateFileA("abc.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); ``` 这次调用发生在 Hook 安装之前,所以会正常执行。 #### 3. 安装 Hook ```cpp char* detour = (char*)&MY_CreateFileA; DWORD oldProtect; // 修改内存属性为可读可写可执行 if (!VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &oldProtect)) { printf("VirtualProtect false\n"); return 0; } // 备份原始5字节 memcpy(backups_asm, target, 5); // 构建JMP指令 unsigned char jmp_0xe9 = 0xE9; unsigned int jmp_addr = detour - (target + 5); // 写入JMP指令 memcpy(target, &jmp_0xe9, 1); memcpy(target + 1, &jmp_addr, 4); // 恢复内存属性 VirtualProtect(target, 5, oldProtect, &oldProtect); ``` #### 4. 第二次调用(Hook 后) ```cpp printf("第二次调用CreateFileA \n"); hFile = CreateFileA("bcd.txt", ...); ``` 此时调用流程变为: ``` CreateFileA() ↓ (CPU执行到入口,发现JMP指令) ↓ MY_CreateFileA() ← 这里执行自定义逻辑 ↓ (恢复原指令) ↓ 原始CreateFileA() ← 真正创建文件 ↓ 返回并可能重新安装Hook ``` --- ## 完整执行流程图 ``` ┌─────────────────────────────────────────────────────────────┐ │ 程序启动 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 第一次调用 CreateFileA("abc.txt") │ │ - CPU 正常执行 CreateFileA 入口指令 │ │ - 创建文件 abc.txt │ │ - 输出: "第一次调用CreateFileA" │ │ - 输出: "CreateFileA success" │ │ - 返回文件句柄 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 安装 Hook │ │ 1. VirtualProtect(..., PAGE_EXECUTE_READWRITE, ...) │ │ 2. 保存原入口 5 字节: memcpy(backups_asm, target, 5) │ │ 3. 计算偏移量: jmp_addr = MY_CreateFileA - (target + 5) │ │ 4. 写入 JMP: memcpy(target, "\xE9\xXX\xXX\xXX\xXX") │ │ 5. VirtualProtect(..., oldProtect, ...) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 第二次调用 CreateFileA("bcd.txt") │ │ - CPU 读取 target 处的字节: 0xE9 │ │ - CPU 识别为 JMP 指令,读取偏移量 │ │ - CPU 跳转到 MY_CreateFileA │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ MY_CreateFileA() │ │ - 打印参数: "MY_CreateFileA: bcd.txt" │ │ - 显示 MessageBox │ │ - 恢复原指令: memcpy(target, backups_asm, 5) │ │ - 调用原始 CreateFileA("bcd.txt") │ │ - 返回文件句柄 │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 软件架构 - **编程语言**: C++ - **开发工具**: Visual Studio 2019+ - **目标平台**: Windows x86/x64 --- ## 安装教程 1. 安装 Visual Studio 2019 或更高版本 2. 安装 Windows SDK 3. 使用 Visual Studio 打开 `Project1/Project1.sln` 4. 分别编译 Project1 和 Project2 项目 --- ## 使用说明 1. **编译项目**: - 打开 `Project1.sln` - 选择 Release 或 Debug 配置 - 编译整个解决方案 2. **运行测试**: - 运行 Project2,查看 Hook 效果 - 观察控制台输出和弹出的 MessageBox 3. **关键代码位置**: - Hook 实现:`Project1/Project2/代码实现.cpp:39-105` - 自定义函数:`Project1/Project2/代码实现.cpp:10-37` --- ## 示例输出 ``` 第一次调用CreateFileA CreateFileA success 第二次调用CreateFileA MY_CreateFileA: bcd.txt ``` --- ## 注意事项 1. **内存保护**:Inline Hook 需要修改内存中的代码,需要适当的内存保护权限 2. **多线程**:实际使用中需要处理多线程安全问题,使用互斥锁或临界区 3. **异常处理**:Hook 可能会导致程序崩溃,需要完善的异常处理 4. **安全软件**:部分安全软件可能会拦截此类行为 5. **x64 架构**:x64 架构下 JMP 指令更长(需要 14 字节),实现更复杂 6. **指令长度**:本示例使用 5 字节,如果被 Hook 的函数入口小于 5 字节会有问题 --- ## 扩展阅读 - **VEH/SEH**:向量异常处理/结构化异常处理,用于更安全的 Hook - **IAT Hook**:通过修改导入地址表实现 Hook(只对导入函数有效) - **EAT Hook**:通过修改导出地址表实现 Hook - **MinHook**:成熟的 Hook 库,使用跳板(Trampoline)技术,支持 x86/x64 --- ## 参与贡献 1. Fork 本仓库 2. 新建 Feat_xxx 分支 3. 提交代码 4. 新建 Pull Request