概述:Detours 的说明和简单使用

Detours

Detours 是微软提供的一套工具,主要用于win32 API的拦截

Github:microsoft/Detours: Detours is a software package for monitoring and instrumenting API calls on Windows. It is distributed in source code form.

仓库中有挺多示例程序可供参考。

使用 Detours 的大致流程:

在 Detours 的工作过程中,DetourTransactionBegin 函数首先用于启动一个截获(Hook)或解除截获(UnHook)的过程,这个过程中可能包含对多个函数的修改。然后,开发者可以使用 DetourAttachDetourAttachEx 函数将 Detour 钩子附加到目标函数上,或使用 DetourDetach 函数将钩子从目标函数中分离出来。

相关函数说明

DetourAttach 函数说明

DetourAttach 是最常用的附加函数,将 Detour 钩子附加到目标函数上。

LONG DetourAttach(
    _Inout_ PVOID *ppPointer,   // 指向目标函数指针的指针(会被修改为指向 trampoline)
    _In_    PVOID  pDetour      // Detour 函数地址
);
  • ppPointer:传入时指向目标函数原地址,成功后会被修改为指向 trampoline(跳板代码),可在 Detour 函数中通过它调用原始函数
  • pDetour:你的 Hook 函数地址,签名必须与目标函数一致
  • 返回值:NO_ERROR 表示成功

⚠️ DetourAttach 内部使用默认参数(5 字节跳转),对大多数 x86/x64 函数已足够。


DetourAttachEx 函数说明

DetourAttachExDetourAttach 的扩展版本,提供了对 Hook 过程更精细的控制,允许获取额外的诊断信息和控制桩(stub)的大小。

函数原型

LONG DetourAttachEx(
    _Inout_  PVOID   *ppPointer,    // 指向目标函数指针的指针
    _In_     PVOID    pDetour,      // Detour 函数地址
    _Out_    PDETOUR_TRAMPOLINE *ppRealTrampoline,  // 接收 trampoline 可执行代码的指针
    _Out_    PVOID   *ppRealTarget,  // 接收目标函数原始入口点
    _Out_    PVOID   *ppDetour       // 接收 detour 函数入口点
);

参数详解

参数方向说明
ppPointerIn/OutDetourAttach 相同。传入目标函数地址指针,成功后被修改为指向 trampoline(可用于调用原函数)
pDetourIn你的 Hook 函数地址
ppRealTrampolineOut接收 trampoline 可执行代码块的指针。trampoline 是 Detours 动态分配的一段可执行内存,包含被覆盖的原始指令 + 跳转回原函数剩余部分的代码。这个指针可以用于直接执行 trampoline 代码,而无需通过 ppPointer 间接调用
ppRealTargetOut接收目标函数的 原始入口地址(即 Hook 前的函数起始地址)。这个值在 Hook 前后不变,始终指向原始函数的入口点
ppDetourOut接收 Detour 函数的 入口地址。这个值在 Hook 前后不变,始终指向你的 Hook 函数入口

返回值

  • NO_ERROR(0):成功
  • ERROR_INVALID_BLOCK:目标函数代码段过短,无法放置跳转指令
  • ERROR_INVALID_DATA:目标函数已有其他 Hook 或代码格式不支持
  • 其他 Win32 错误码

与 DetourAttach 的区别

特性DetourAttachDetourAttachEx
基本附加功能
获取 trampoline 代码指针ppRealTrampoline
获取原始入口地址ppRealTarget
获取 Detour 入口地址ppDetour
诊断/调试支持有限完整

使用场景

  1. 调试与诊断:获取 trampoline 和原始入口地址,用于验证 Hook 是否正确安装
  2. 多层 Hook 链:当你需要在多个 Hook 之间手动跳转时,ppRealTrampoline 提供了直接访问 trampoline 代码的能力
  3. Hook 验证:对比 ppRealTargetppPointer 的值,确认 Hook 已正确生效
  4. 安全工具开发:在安全监控场景中,需要精确追踪每个 Hook 的安装状态和位置

使用示例

#include <iostream>
#include <Windows.h>
#include "detours.h"
#pragma comment(lib, "detours.lib")
 
using namespace std;
 
// 原函数指针
void (*OldGetLocalTime)(LPSYSTEMTIME) = GetLocalTime;
 
// Hook 函数
void NewGetLocalTime(LPSYSTEMTIME lpSystemTime) {
    OldGetLocalTime(lpSystemTime);
    lpSystemTime->wMinute = 52;
}
 
void StartHookEx() {
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
 
    // 扩展参数
    PDETOUR_TRAMPOLINE pRealTrampoline = nullptr;
    PVOID pRealTarget = nullptr;
    PVOID pDetourEntry = nullptr;
 
    LONG hr = DetourAttachEx(
        &(PVOID&)OldGetLocalTime,
        NewGetLocalTime,
        &pRealTrampoline,
        &pRealTarget,
        &pDetourEntry
    );
 
    if (hr != NO_ERROR) {
        wprintf(L"DetourAttachEx failed: %d\n", hr);
        DetourTransactionAbort();
        return;
    }
 
    LONG commitHR = DetourTransactionCommit();
    if (commitHR != NO_ERROR) {
        wprintf(L"Transaction commit failed: %d\n", commitHR);
        return;
    }
 
    // 输出诊断信息
    wprintf(L"[Hook 诊断信息]\n");
    wprintf(L"  Trampoline 地址: %p\n", pRealTrampoline);
    wprintf(L"  原始入口地址:    %p\n", pRealTarget);
    wprintf(L"  Detour 入口地址: %p\n", pDetourEntry);
    wprintf(L"  OldGetLocalTime:  %p (指向 trampoline)\n", OldGetLocalTime);
}
 
void EndHookEx() {
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourDetach(&(PVOID&)OldGetLocalTime, NewGetLocalTime);
    DetourTransactionCommit();
}
 
int main() {
    SYSTEMTIME time1, time2;
    GetLocalTime(&time1);
    wprintf(L"Hook 前: %02d:%02d\n", time1.wHour, time1.wMinute);
 
    StartHookEx();
 
    GetLocalTime(&time2);
    wprintf(L"Hook 后: %02d:%02d\n", time2.wHour, time2.wMinute);
 
    EndHookEx();
    return 0;
}

⚠️ 注意事项

  1. ppRealTrampoline 指向可执行内存:该地址可以直接被调用(call),但不应被修改或释放——Detours 在 DetourDetach 时会自动处理
  2. ppRealTarget 不等于 ppPointer 修改后的值ppRealTarget 始终是原始函数入口地址,而 ppPointer 被修改后指向 trampoline 代码块。调用 trampoline(通过 ppPointer)等效于调用原始函数,但 trampoline 可能包含额外的指令修复
  3. 所有扩展参数都可以传 NULL:如果你不需要某个输出参数,传 NULL 即可,功能退化为 DetourAttach
  4. 线程安全:与 DetourAttach 一样,必须在事务内调用

DetourUpdateThread 函数说明

DetourUpdateThread 函数是 Detours 库中的一个重要函数,用于在 Detour 事务处理过程中将特定的线程列入更新范围。Detours 是一个用于在运行时修改二进制代码(如函数钩子)的库,它允许开发者在不修改原始代码的情况下,拦截和修改函数的执行流程。

DetourUpdateThread 函数的作用是在这个事务过程中,将一个或多个线程列入需要更新的范围。这是因为在多线程环境中,直接修改函数的代码可能会导致竞态条件,即一个线程正在执行原始代码时,另一个线程可能已经修改了该代码。通过 DetourUpdateThread 将线程列入更新范围,Detours 库可以确保在事务提交时,所有相关线程的代码都被正确地更新,从而避免竞态条件。

具体来说,DetourUpdateThread 函数的作用是:

  1. 将指定的线程加入到 Detour 事务的更新列表中。
  2. 在事务提交时,Detours 库会遍历这个更新列表,并对列表中的每个线程执行必要的代码更新操作。

需要注意的是,在调用 DetourTransactionCommitDetourTransactionCommitEx 函数提交事务之前,所有的附加、分离和线程更新操作都不会生效。一旦事务被提交,所有的修改就会立即生效,所有相关线程的代码都会被更新以反映这些修改。

因此,DetourUpdateThread 函数是 Detours 库中用于确保多线程环境下代码修改一致性和安全性的重要工具之一。

可以看下 DetourUpdateThread 的源码,如下所示:

LONG WINAPI DetourUpdateThread(_In_ HANDLE hThread)
{
    LONG error;
 
    // If any of the pending operations failed, then we don't need to do this.
    if (s_nPendingError != NO_ERROR) {
        return s_nPendingError;
    }
 
    // Silently (and safely) drop any attempt to suspend our own thread.
    if (hThread == GetCurrentThread()) {
        return NO_ERROR;
    }
 
    DetourThread *t = new NOTHROW DetourThread;
    if (t == NULL) {
        error = ERROR_NOT_ENOUGH_MEMORY;
      fail:
        if (t != NULL) {
            delete t;
            t = NULL;
        }
        s_nPendingError = error;
        s_ppPendingError = NULL;
        DETOUR_BREAK();
        return error;
    }
 
    if (SuspendThread(hThread) == (DWORD)-1) {
        error = GetLastError();
        DETOUR_BREAK();
        goto fail;
    }
 
    t->hThread = hThread;
    t->pNext = s_pPendingThreads;
    s_pPendingThreads = t;
 
    return NO_ERROR;
}
 

hookDemo

修改 GetLocalTime ,使其返回的分钟数为 52

#include <iostream>
#include <Windows.h>
//包含Detour的头文件和库文件
#include "detours.h"
#pragma comment (lib,"detours.lib")
using namespace std;
 
//保存函数原型(用指针存储要拦截的API)
void (*OldGetLocalTime)(LPSYSTEMTIME) = GetLocalTime;
 
//拦截后要执行的操作(这里是将时间的分改为52)
void NewGetLocalTime(LPSYSTEMTIME lpSystemTime) {
	OldGetLocalTime(lpSystemTime);
	lpSystemTime->wMinute = 52;
}
 
//下钩子函数
void StartHook() {
	//开始事务
	DetourTransactionBegin();
	//更新线程信息
	DetourUpdateThread(GetCurrentThread());
	//将拦截的函数附加到原函数的地址上
	DetourAttach(&(PVOID&)OldGetLocalTime, NewGetLocalTime);
	//结束事务
	DetourTransactionCommit();
}
 
//撤钩子函数
void EndHook() {
	//开始detours事务
	DetourTransactionBegin();
	//更新线程信息 
	DetourUpdateThread(GetCurrentThread());
	//将拦截的函数从原函数的地址上解除
	DetourDetach(&(PVOID&)OldGetLocalTime, NewGetLocalTime);
	//结束detours事务
	DetourTransactionCommit();
}
 
int main()
{
	//获取本地时间
	SYSTEMTIME time, time2;
	GetLocalTime(&time);
	cout << time.wHour << ":" << time.wMinute << endl;
	//下钩子
	StartHook();
 
	//下钩子后再次获取本地时间
	GetLocalTime(&time2);
	cout << time2.wHour << ":" << time2.wMinute << endl;
 
	//撤钩子
	EndHook();
	return 0;
}