模块 59 - API Hooking - Detours 库
恶意软件开发课程 - API Hooking - Detours 库
模块 59 - API Hooking - Detours 库#
[!IMPORTANT] 本知识库声明
本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示与实践心得。
- 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
- 版权归属:原始知识产权归原作者/官方所有。
- 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程。
- 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。
API Hooking - Detours 库#
简介#
Detours Hooking Library ↗ 是由微软研究院开发的一个软件库,用于在 Windows 中拦截和重定向函数调用。该库可以将特定函数的调用重定向到用户定义的替代函数,从而执行额外任务或修改原始函数的行为。Detours 通常用于 C/C++ 程序,并同时支持 32 位和 64 位应用程序。
该库的维基页面可以在 这里 ↗ 找到。
事务 (Transactions)#
Detours 库通过将目标函数(即要被 Hook 的函数)的前几条指令替换为指向用户自定义函数(即要执行的 Hook 函数)的无条件跳转指令来实现重定向。这种无条件跳转也被称为“跳板”。
该库使用 事务 (Transactions) 系统来安装和卸载 Hook。事务允许将多个 Hook 操作编组为一个单一单元进行应用,这在需要同时修改多个程序行为时非常有用,且方便在出错时撤销所有更改。 一个完整的流程通常包括:开始事务 -> 添加 Hook -> 提交事务。只有在提交(Commit)后,更改才会正式生效。
使用 Detours 库#
要使用 Detours 库,首先需要从 GitHub 下载源码并编译,以获得所需的静态库文件(.lib)。此外,项目中必须包含 detours.h 头文件。详情可参考 Detours 维基中的 Using Detours ↗ 部分。
32 位与 64 位的兼容性#
为了在不同架构下自动包含正确的库文件,我们可以使用预处理指令:
// 如果编译为 64 位
#ifdef _M_X64
#pragma comment (lib, "detoursx64.lib")
#endif
// 如果编译为 32 位
#ifdef _M_IX86
#pragma comment (lib, "detoursx86.lib")
#endif cDetours 核心 API#
执行 Hook 的第一步始终是获取目标 WinAPI 函数的地址。在本模块中,我们将以 MessageBoxA 为例。以下是库提供的核心 API:
- DetourTransactionBegin ↗ - 开始一个新的事务。
- DetourUpdateThread ↗ - 将当前线程列入事务更新名单。
- DetourAttach ↗ - 在事务中安装 Hook。
- DetourDetach ↗ - 在事务中移除 Hook。
- DetourTransactionCommit ↗ - 提交当前事务。
这些函数返回 LONG 类型值。执行成功返回 NO_ERROR (0),失败则返回非零值。
替换被挂钩的 API#
我们需要创建一个替代函数。该函数应具有与原始 API 相同的数据类型和参数列表。这允许我们检查或修改参数。
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
// 我们可以在这里检查或修改参数:hWnd, lpText, lpCaption, uType
}c💡 知识扩展:参数限制
替代函数的参数个数可以少于原始函数(虽然不推荐,因为无法获取完整现场),但 绝对不能多于 原始函数。否则会导致堆栈访问越界,触发访问违规异常(Access Violation)。
无限循环问题#
这是 Hook 技术中最常见的一个坑:如果在我们的自定义函数内部又直接调用了原始的 MessageBoxA,由于它已经被挂钩,执行权会再次跳回到我们的函数中,从而形成 死循环,最终导致程序崩溃(栈溢出)。
// 错误示范:会导致死循环
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
// 调用 MessageBoxA 时会再次触发 Hook 逻辑,陷入死循环
return MessageBoxA(hWnd, "已被劫持内容", lpCaption, uType);
}c解决方案 1:保存原始函数指针#
Detours 推荐的做法是:在 Hook 生效前,将原始函数的地址保存到一个全局变量中。在自定义函数内部,始终调用这个全局指针。
// 定义指向原始函数的全局指针
fnMessageBoxA g_pMessageBoxA = MessageBoxA;
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
// 调用未经挂钩的“备份”指针,从而避免死循环
return g_pMessageBoxA(hWnd, "修改后的内容", lpCaption, uType);
}c解决方案 2:使用功能相同的不同函数#
例如,Hook 了 MessageBoxA 之后,在内部调用 MessageBoxW。
安装与卸载 Hook 流程#
// 必须在 Hook 前保存原始指针
fnMessageBoxA g_pMessageBoxA = MessageBoxA;
// 安装 Hook
BOOL InstallHook() {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
// 将 g_pMessageBoxA 指向的地址(原始地址)修改为 MyMessageBoxA(新地址)
DetourAttach((PVOID)&g_pMessageBoxA, MyMessageBoxA);
return (DetourTransactionCommit() == NO_ERROR);
}
// 卸载 Hook
BOOL Unhook() {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach((PVOID)&g_pMessageBoxA, MyMessageBoxA);
return (DetourTransactionCommit() == NO_ERROR);
}c演示#
- 调用第一次:正常弹出框。
- 安装 Hook。
- 调用第二次:内容被修改为 “different lpText”(Hook 生效)。
- 卸载 Hook。
- 调用第三次:恢复正常。

🎯 总结#
在本模块中,我们学习了:
- Detours 库入门:掌握了事务性 Hook 的操作流程。
- 安全性与健壮性:理解了“死循环风险”并学会了通过保存原始指针来规避它。
- 动态拦截:实现了在不修改二进制文件的情况下动态篡改 API 参数的能力。
💡 关键要点
- 全局指针的重要性:
g_pMessageBoxA既是DetourAttach的目标,也是你调用原始函数的唯一安全入口。- 事务一致性:记得每一个
Begin都要对应一个Commit。
📚 下一步学习
下一个模块将介绍 API Hooking - Minhook Library。我们将学习另一个更为轻量且深受恶意软件作者喜爱的开源 Hook 库!