Windows hook框架Detours踩坑
字数 2181 2025-08-22 12:22:15
Detours框架使用详解与踩坑指南
一、Detours简介
Detours是微软官方提供的Windows平台hook框架,相比Android上种类繁多的hook框架,Windows上可选的hook框架较少。Detours通过修改目标函数的前几个字节,将其替换为跳转指令,实现函数拦截和替换。
二、下载与编译
-
源码获取:
- 官方GitHub:https://github.com/microsoft/Detours/releases/tag/v4.0.1
-
编译步骤:
- 解压下载的源码包
- 打开VS的Native Tools Command Prompt命令行
- 编译64位:使用x64开头的命令行
- 编译32位:使用x86开头的命令行
- cd到Detours的src目录
- 执行nmake命令进行编译
-
编译结果:
- 32位:生成lib.X86目录,包含detours.lib静态库
- 64位:生成lib.X64目录,包含detours.lib静态库
- 头文件位于include目录下的detours.h
三、项目配置
-
基本配置步骤:
- 将detours.lib和detours.h拷贝到项目目录
- 添加detours.h到头文件
- 在项目属性中进行配置
-
关键配置项:
- 选择正确的平台(32位或64位)
- 在VC++目录→库目录中添加detours.lib所在目录
- 取消SDL检查(避免scanf/sprintf等函数报错)
- 关闭符合模式(避免指针强转报错)
-
代码中需要添加:
#include "detours.h" // 导入Detours头文件
#pragma comment(lib, "detours.lib") // 导入Detours库
四、基本使用
1. 简单hook示例(hook puts函数)
#include <stdio.h>
#include "detours.h"
#pragma comment(lib, "detours.lib")
// 原始函数指针
static int (*RealPuts)(const char* str) = puts;
// 替换函数
void MyPuts(const char* str) {
printf("%s123\n", str);
RealPuts("hooked");
}
// hook安装函数
void hookPuts() {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID)RealPuts, MyPuts);
DetourTransactionCommit();
}
// hook卸载函数
void unHook() {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID)RealPuts, MyPuts);
DetourTransactionCommit();
}
// DLL入口函数
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
hookPuts();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
unHook();
break;
}
return TRUE;
}
2. Detours核心函数
DetourTransactionBegin(): 开始一个hook事务DetourUpdateThread(GetCurrentThread()): 更新当前线程状态DetourAttach(&(PVOID)OldFunc, NewFunc): 安装hookDetourDetach(&(PVOID)OldFunc, NewFunc): 卸载hookDetourTransactionCommit(): 提交hook事务
五、高级使用技巧
1. 获取函数指针的正确方式
问题:直接使用函数名获取指针可能导致hook失败,因为Debug和Release模式下可能使用不同的DLL(如ucrtbased.dll和ucrtbase.dll)
解决方案:使用DetourFindFunction查找目标程序加载的动态链接库中的函数
static int (*RealPuts)(const char* str) =
(int (*)(const char*))DetourFindFunction("ucrtbased.dll", "puts");
2. hook非导出函数
步骤:
- 使用IDA找到目标函数的偏移量(函数地址 - 模块基址)
- 在代码中动态计算函数地址
DWORD addOffset = 0x1100; // 函数偏移
DWORD baseAddr = (DWORD)GetModuleHandleA("target.exe");
PVOID pOriginAdd = (PVOID)(addOffset + baseAddr);
3. 函数调用约定问题
Detours hook时必须保持目标函数和替换函数的调用约定一致,否则会导致堆栈不平衡,程序崩溃。
常见调用约定:
-
__cdecl (WINAPIV)
- 参数通过栈传递
- 调用方清理堆栈
- 默认调用约定
-
__stdcall (WINAPI)
- 参数通过栈传递
- 被调用方清理堆栈
- Windows API常用
-
__fastcall
- 前两个参数通过ecx和edx寄存器传递
- 其余参数通过栈传递
- 被调用方清理堆栈
-
__thiscall
- 类成员函数专用
- this指针通过ecx传递
- 其余参数通过栈传递
- 被调用方清理堆栈
调用约定不一致的解决方案:
thiscall的hook方法:
int __stdcall MyFunc(int a, int b) {
__asm { push ecx } // 保存this指针
// 函数逻辑
__asm { pop ecx } // 恢复this指针
// 调用原函数
int d = ((int(__stdcall*)(int, int))g_pOriginAdd)(a, b);
return d;
}
六、Detours工作原理
-
计算需要修改的目标函数字节数n
- jmp指令需要5字节
- 确保不会截断完整指令
-
计算Trampoline(跳板)函数地址
- 目标函数地址 + n
-
构建Trampoline函数
- 拷贝目标函数前n字节
- 添加jmp指令跳回原函数剩余部分
-
修改目标函数
- 前5字节改为jmp到新函数
- 多余部分(n>5)填充0xCC
-
通过Trampoline函数调用原函数
七、常见问题与解决方案
-
函数指针获取失败
- 确保使用正确的DLL名称和函数名
- 对于C++函数,使用正确的修饰名(可通过IDA查看)
-
hook后程序崩溃
- 检查调用约定是否一致
- 检查参数数量和类型是否匹配
- 检查堆栈是否平衡
-
hook不生效
- 确认目标函数没有被内联(使用
__declspec(noinline)) - 确认hook安装时机正确(通常在DLL_PROCESS_ATTACH时安装)
- 确认目标函数没有被内联(使用
-
64位程序hook问题
- 64位下函数调用约定与32位不同
- 确保使用64位编译的Detours库
八、最佳实践
- 尽量使用
DetourFindFunction获取函数指针 - 保持目标函数和替换函数的调用约定一致
- 对于非导出函数,使用基址+偏移量的方式获取地址
- 在DLL_PROCESS_ATTACH时安装hook,在DLL_PROCESS_DETACH时卸载hook
- 对于简单的hook需求,可以不必实现卸载逻辑
- 使用
__declspec(noinline)防止目标函数被内联优化
九、总结
Detours是一个强大的Windows平台hook框架,但在使用过程中需要注意:
- 函数指针的正确获取方式
- 调用约定的匹配
- 堆栈平衡的维护
- 不同编译模式下的差异
通过理解Detours的工作原理和遵循最佳实践,可以有效地在各种场景下实现函数hook,满足调试、监控、扩展等需求。