Detours实战教程:DetourAttach与DetourDetach函数使用指南
引言:API拦截的痛点与解决方案
你是否曾需要监控应用程序的API调用?是否在调试时希望跟踪特定函数的执行流程?Detours库为Windows平台提供了强大的API拦截能力,但许多开发者在使用过程中常遇到拦截不稳定、内存泄漏或多线程冲突等问题。本文将系统讲解DetourAttach与DetourDetach的核心原理与实战技巧,帮助你轻松掌握API拦截技术。
读完本文后,你将能够:
- 理解Detours事务模型的工作原理
- 正确使用DetourAttach与DetourDetach进行API拦截与恢复
- 处理多线程环境下的拦截问题
- 避免常见的内存泄漏与冲突陷阱
- 掌握高级拦截技巧与最佳实践
Detours核心概念解析
事务模型(Transaction Model)
Detours采用事务(Transaction)机制管理API拦截操作,确保多线程环境下的稳定性。事务模型包含三个关键步骤:
- 原子性:事务内的所有拦截操作要么全部成功,要么全部失败
- 隔离性:事务执行过程中不会被其他线程干扰
- 一致性:确保拦截状态的正确性与完整性
DetourAttach与DetourDetach工作原理
DetourAttach与DetourDetach通过修改目标函数的前几条指令,将执行流程重定向到自定义函数(Detour函数)。其内部工作流程如下:
- 跳板函数(Trampoline):保存原始函数指令,负责将执行流程从拦截函数转回原始函数
- 拦截函数(Detour):自定义实现,可在调用原始函数前后添加额外逻辑
- 原始函数(Target):被拦截的API函数
DetourAttach函数详解
函数原型与参数
LONG WINAPI DetourAttach(
_Inout_ PVOID *ppPointer,
_In_ PVOID pDetour
);
| 参数 | 类型 | 描述 |
|---|---|---|
| ppPointer | PVOID* | 指向原始函数指针的指针 |
| pDetour | PVOID | 拦截函数的指针 |
返回值说明
| 返回值 | 含义 |
|---|---|
| NO_ERROR (0) | 操作成功 |
| ERROR_INVALID_PARAMETER (87) | 参数无效 |
| ERROR_NOT_ENOUGH_MEMORY (8) | 内存不足 |
| ERROR_GEN_FAILURE (31) | 通用失败 |
| ERROR_DYNAMIC_CODE_BLOCKED (1655) | 动态代码生成被阻止 |
使用步骤
- 声明函数指针类型:匹配原始函数签名
- 定义原始函数指针与拦截函数
- 初始化原始函数指针
- 创建事务并附加拦截
代码示例:拦截SleepEx函数
// 1. 声明函数指针类型
typedef DWORD (WINAPI *SleepExFunc)(DWORD dwMilliseconds, BOOL bAlertable);
// 2. 定义原始函数指针与拦截函数
SleepExFunc TrueSleepEx = SleepEx;
DWORD WINAPI TimedSleepEx(DWORD dwMilliseconds, BOOL bAlertable) {
DWORD dwBeg = GetTickCount();
DWORD ret = TrueSleepEx(dwMilliseconds, bAlertable); // 调用原始函数
DWORD dwEnd = GetTickCount();
// 添加自定义逻辑:记录睡眠时长
InterlockedExchangeAdd(&dwSlept, dwEnd - dwBeg);
return ret;
}
// 3. 在DLL_PROCESS_ATTACH中附加拦截
if (dwReason == DLL_PROCESS_ATTACH) {
DetourRestoreAfterWith();
// 4. 创建事务并附加拦截
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
// 关键点:传递原始函数指针的地址
DetourAttach(&(PVOID&)TrueSleepEx, TimedSleepEx);
LONG error = DetourTransactionCommit();
if (error == NO_ERROR) {
printf("成功拦截SleepEx()\n");
} else {
printf("拦截失败: %ld\n", error);
}
}
常见问题与解决方案
问题1:函数签名不匹配导致崩溃
原因:拦截函数与原始函数的调用约定或参数列表不匹配
解决方案:
- 使用
WINAPI宏确保__stdcall调用约定 - 严格匹配参数类型与数量
- 对于变参函数,使用DetourAttachEx处理
问题2:多线程环境下的竞争条件
解决方案:
// 为所有线程更新拦截信息
HANDLE hThread = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
THREADENTRY32 te32;
te32.dwSize = sizeof(THREADENTRY32);
Thread32First(hThread, &te32);
do {
if (te32.th32OwnerProcessID == GetCurrentProcessId()) {
HANDLE h = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
if (h) {
DetourUpdateThread(h); // 为每个线程更新
CloseHandle(h);
}
}
} while (Thread32Next(hThread, &te32));
DetourDetach函数详解
函数原型与参数
LONG WINAPI DetourDetach(
_Inout_ PVOID *ppPointer,
_In_ PVOID pDetour
);
参数与DetourAttach完全相同,作用是移除之前附加的拦截。
使用场景
- DLL_PROCESS_DETACH时清理资源
- 动态启用/禁用拦截功能
- 程序退出前恢复原始函数
代码示例:移除SleepEx拦截
if (dwReason == DLL_PROCESS_DETACH) {
// 创建事务并移除拦截
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueSleepEx, TimedSleepEx);
LONG error = DetourTransactionCommit();
printf("已移除SleepEx拦截 (结果=%ld), 总睡眠时间: %ld ticks\n", error, dwSlept);
}
常见问题与解决方案
问题1:卸载时崩溃或死锁
原因:未正确处理多线程环境下的Detach操作
解决方案:
- 确保在Detach前暂停所有可能调用被拦截函数的线程
- 使用DetourUpdateThread更新所有活跃线程
- 在DLL_PROCESS_DETACH中处理时检查保留的线程
问题2:Detach后仍执行拦截函数
原因:事务提交失败或存在未更新的线程
解决方案:
PVOID *ppFailedPointer = NULL;
LONG error = DetourTransactionCommitEx(&ppFailedPointer);
if (error != NO_ERROR) {
if (ppFailedPointer) {
printf("拦截失败的函数: %p\n", *ppFailedPointer);
// 处理失败情况
}
}
高级技巧与最佳实践
1. 使用DetourAttachEx获取详细信息
PDETOUR_TRAMPOLINE pTrampoline = NULL;
PVOID pRealTarget = NULL;
PVOID pRealDetour = NULL;
LONG error = DetourAttachEx(
&(PVOID&)TrueSleepEx,
TimedSleepEx,
&pTrampoline,
&pRealTarget,
&pRealDetour
);
if (error == NO_ERROR) {
printf("跳板函数地址: %p\n", pTrampoline);
printf("真实目标地址: %p\n", pRealTarget);
printf("真实拦截函数地址: %p\n", pRealDetour);
}
2. 处理动态加载的函数
// 使用DetourFindFunction定位动态函数
PVOID pFunc = DetourFindFunction("kernel32.dll", "CreateFileA");
if (pFunc) {
DetourAttach(&pFunc, MyCreateFileA);
}
3. 多函数拦截的事务管理
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
// 附加多个函数拦截
DetourAttach(&(PVOID&)TrueSleepEx, TimedSleepEx);
DetourAttach(&(PVOID&)TrueCreateFile, MyCreateFile);
DetourAttach(&(PVOID&)TrueReadFile, MyReadFile);
LONG error = DetourTransactionCommit();
if (error == NO_ERROR) {
printf("所有函数拦截成功\n");
} else {
printf("拦截失败: %ld\n", error);
}
4. 内存管理最佳实践
- 使用DetourSetRetainRegions控制内存区域保留策略
- 在Detach后释放不再需要的跳板函数内存
- 监控内存使用,避免内存泄漏
// 设置Detours保留内存区域
DetourSetRetainRegions(TRUE); // 保留区域,适合频繁Attach/Detach
// 或
DetourSetRetainRegions(FALSE); // 自动释放,适合一次性拦截
实战案例:文件操作监控器
下面实现一个完整的文件操作监控器,拦截CreateFile、ReadFile和WriteFile三个API:
#include <windows.h>
#include <detours.h>
#include <stdio.h>
// 函数指针类型定义
typedef HANDLE (WINAPI *CreateFileFunc)(LPCSTR lpFileName, DWORD dwDesiredAccess,
DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
typedef BOOL (WINAPI *ReadFileFunc)(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
typedef BOOL (WINAPI *WriteFileFunc)(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
// 原始函数指针
CreateFileFunc TrueCreateFile = CreateFileA;
ReadFileFunc TrueReadFile = ReadFile;
WriteFileFunc TrueWriteFile = WriteFile;
// 拦截函数实现
HANDLE WINAPI MyCreateFile(LPCSTR lpFileName, DWORD dwDesiredAccess,
DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
printf("文件创建: %s\n", lpFileName);
return TrueCreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
BOOL WINAPI MyReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped) {
BOOL ret = TrueReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
if (ret && lpNumberOfBytesRead && *lpNumberOfBytesRead > 0) {
printf("读取文件: 句柄=%p, 字节数=%d\n", hFile, *lpNumberOfBytesRead);
}
return ret;
}
BOOL WINAPI MyWriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) {
BOOL ret = TrueWriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
if (ret && lpNumberOfBytesWritten) {
printf("写入文件: 句柄=%p, 字节数=%d\n", hFile, *lpNumberOfBytesWritten);
}
return ret;
}
// DLL入口函数
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved) {
if (DetourIsHelperProcess()) {
return TRUE;
}
if (dwReason == DLL_PROCESS_ATTACH) {
DetourRestoreAfterWith();
// 开始事务
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
// 附加所有拦截
DetourAttach(&(PVOID&)TrueCreateFile, MyCreateFile);
DetourAttach(&(PVOID&)TrueReadFile, MyReadFile);
DetourAttach(&(PVOID&)TrueWriteFile, MyWriteFile);
LONG error = DetourTransactionCommit();
if (error == NO_ERROR) {
printf("文件操作监控器已加载\n");
} else {
printf("加载失败: %ld\n", error);
}
}
else if (dwReason == DLL_PROCESS_DETACH) {
// 开始事务
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
// 移除所有拦截
DetourDetach(&(PVOID&)TrueCreateFile, MyCreateFile);
DetourDetach(&(PVOID&)TrueReadFile, MyReadFile);
DetourDetach(&(PVOID&)TrueWriteFile, MyWriteFile);
DetourTransactionCommit();
printf("文件操作监控器已卸载\n");
}
return TRUE;
}
总结与展望
本文详细介绍了DetourAttach与DetourDetach函数的使用方法,从基础概念到高级技巧,涵盖了API拦截的核心知识点。通过事务模型确保拦截操作的原子性与一致性,是Detours库稳定性的关键。在实际应用中,需特别注意多线程环境下的线程更新与同步问题,以及内存资源的正确管理。
未来,随着Windows平台安全机制的不断增强,API拦截技术也将面临新的挑战。Detours库持续更新以应对这些变化,建议开发者关注官方最新动态,及时更新使用的库版本。
掌握DetourAttach与DetourDetach的使用,将为你的Windows开发工具箱增添强大的API拦截能力,无论是调试、监控还是扩展应用功能,都能游刃有余。
参考资料
- Microsoft Research Detours官方文档
- Detours源代码(https://gitcode.com/gh_mirrors/de/Detours)
- Windows API参考文档
- 《Windows核心编程》(第5版)关于API拦截的章节
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



