Windows hook框架Detours踩坑
字数 2181 2025-08-22 12:22:15

Detours框架使用详解与踩坑指南

一、Detours简介

Detours是微软官方提供的Windows平台hook框架,相比Android上种类繁多的hook框架,Windows上可选的hook框架较少。Detours通过修改目标函数的前几个字节,将其替换为跳转指令,实现函数拦截和替换。

二、下载与编译

  1. 源码获取:

    • 官方GitHub:https://github.com/microsoft/Detours/releases/tag/v4.0.1
  2. 编译步骤:

    • 解压下载的源码包
    • 打开VS的Native Tools Command Prompt命令行
      • 编译64位:使用x64开头的命令行
      • 编译32位:使用x86开头的命令行
    • cd到Detours的src目录
    • 执行nmake命令进行编译
  3. 编译结果:

    • 32位:生成lib.X86目录,包含detours.lib静态库
    • 64位:生成lib.X64目录,包含detours.lib静态库
    • 头文件位于include目录下的detours.h

三、项目配置

  1. 基本配置步骤:

    • 将detours.lib和detours.h拷贝到项目目录
    • 添加detours.h到头文件
    • 在项目属性中进行配置
  2. 关键配置项:

    • 选择正确的平台(32位或64位)
    • 在VC++目录→库目录中添加detours.lib所在目录
    • 取消SDL检查(避免scanf/sprintf等函数报错)
    • 关闭符合模式(避免指针强转报错)
  3. 代码中需要添加:

#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): 安装hook
  • DetourDetach(&(PVOID)OldFunc, NewFunc): 卸载hook
  • DetourTransactionCommit(): 提交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非导出函数

步骤

  1. 使用IDA找到目标函数的偏移量(函数地址 - 模块基址)
  2. 在代码中动态计算函数地址
DWORD addOffset = 0x1100; // 函数偏移
DWORD baseAddr = (DWORD)GetModuleHandleA("target.exe");
PVOID pOriginAdd = (PVOID)(addOffset + baseAddr);

3. 函数调用约定问题

Detours hook时必须保持目标函数和替换函数的调用约定一致,否则会导致堆栈不平衡,程序崩溃。

常见调用约定:

  1. __cdecl (WINAPIV)

    • 参数通过栈传递
    • 调用方清理堆栈
    • 默认调用约定
  2. __stdcall (WINAPI)

    • 参数通过栈传递
    • 被调用方清理堆栈
    • Windows API常用
  3. __fastcall

    • 前两个参数通过ecx和edx寄存器传递
    • 其余参数通过栈传递
    • 被调用方清理堆栈
  4. __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工作原理

  1. 计算需要修改的目标函数字节数n

    • jmp指令需要5字节
    • 确保不会截断完整指令
  2. 计算Trampoline(跳板)函数地址

    • 目标函数地址 + n
  3. 构建Trampoline函数

    • 拷贝目标函数前n字节
    • 添加jmp指令跳回原函数剩余部分
  4. 修改目标函数

    • 前5字节改为jmp到新函数
    • 多余部分(n>5)填充0xCC
  5. 通过Trampoline函数调用原函数

七、常见问题与解决方案

  1. 函数指针获取失败

    • 确保使用正确的DLL名称和函数名
    • 对于C++函数,使用正确的修饰名(可通过IDA查看)
  2. hook后程序崩溃

    • 检查调用约定是否一致
    • 检查参数数量和类型是否匹配
    • 检查堆栈是否平衡
  3. hook不生效

    • 确认目标函数没有被内联(使用__declspec(noinline)
    • 确认hook安装时机正确(通常在DLL_PROCESS_ATTACH时安装)
  4. 64位程序hook问题

    • 64位下函数调用约定与32位不同
    • 确保使用64位编译的Detours库

八、最佳实践

  1. 尽量使用DetourFindFunction获取函数指针
  2. 保持目标函数和替换函数的调用约定一致
  3. 对于非导出函数,使用基址+偏移量的方式获取地址
  4. 在DLL_PROCESS_ATTACH时安装hook,在DLL_PROCESS_DETACH时卸载hook
  5. 对于简单的hook需求,可以不必实现卸载逻辑
  6. 使用__declspec(noinline)防止目标函数被内联优化

九、总结

Detours是一个强大的Windows平台hook框架,但在使用过程中需要注意:

  • 函数指针的正确获取方式
  • 调用约定的匹配
  • 堆栈平衡的维护
  • 不同编译模式下的差异

通过理解Detours的工作原理和遵循最佳实践,可以有效地在各种场景下实现函数hook,满足调试、监控、扩展等需求。

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等函数报错) 关闭符合模式(避免指针强转报错) 代码中需要添加: 四、基本使用 1. 简单hook示例(hook puts函数) 2. Detours核心函数 DetourTransactionBegin() : 开始一个hook事务 DetourUpdateThread(GetCurrentThread()) : 更新当前线程状态 DetourAttach(&(PVOID)OldFunc, NewFunc) : 安装hook DetourDetach(&(PVOID)OldFunc, NewFunc) : 卸载hook DetourTransactionCommit() : 提交hook事务 五、高级使用技巧 1. 获取函数指针的正确方式 问题 :直接使用函数名获取指针可能导致hook失败,因为Debug和Release模式下可能使用不同的DLL(如ucrtbased.dll和ucrtbase.dll) 解决方案 :使用 DetourFindFunction 查找目标程序加载的动态链接库中的函数 2. hook非导出函数 步骤 : 使用IDA找到目标函数的偏移量(函数地址 - 模块基址) 在代码中动态计算函数地址 3. 函数调用约定问题 Detours hook时必须保持目标函数和替换函数的调用约定一致,否则会导致堆栈不平衡,程序崩溃。 常见调用约定: __ cdecl (WINAPIV) 参数通过栈传递 调用方清理堆栈 默认调用约定 __ stdcall (WINAPI) 参数通过栈传递 被调用方清理堆栈 Windows API常用 __ fastcall 前两个参数通过ecx和edx寄存器传递 其余参数通过栈传递 被调用方清理堆栈 __ thiscall 类成员函数专用 this指针通过ecx传递 其余参数通过栈传递 被调用方清理堆栈 调用约定不一致的解决方案: thiscall的hook方法 : 六、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,满足调试、监控、扩展等需求。