Detours 是微软开发的一个强大的Windows API钩子库,用于监视和拦截函数调用。它广泛应用于微软产品团队和众多独立软件开发中,旨在无需修改原始代码的情况下实现函数拦截和修改。Detours 在调试、监控、日志记录和性能分析等方面表现出色,已成为开发者的重要工具。本章将指导读者编译并使用Detours库,通过实现一个简单的弹窗替换功能,帮助读者熟悉该库的使用技巧。
Detours 是一个兼容多个Windows
系列操作系统版本(包括 Windows XP
到 Windows 11
)的工具库。它现在在 MIT 开源许可证下发布,简化了开发者的使用许可流程,并允许社区利用开源工具和流程来支持其发展,目前该库的稳定版本为4.0.1
读者可通过如下官方链接自行下载到本地。
- Detours 4.0.1:https://github.com/microsoft/Detours/releases
下载文件后打开目录,其中src
目录下存储的是Detours
库的源代码,而samples
则是一些使用案例,当准备就绪后读者需要打开Visual Studio
开发者命令提示符,你可以从开始菜单中找到Visual Studio Tools
工具菜单,并在其中找到VS20XX x86 本机工具命令提示
或Developer Command Prompt for VS 20XX
字样,此处以x86
为例,在命令提示符中跳转到Detours
源代码目录,运行 nmake
命令执行编译。
如果一切顺利,这将会编译Detours
库并生成所需的二进制文件,其中include
保存有头文件信息,而lib.X86
则包含有detours.lib
库文件,如下图中所示。
接着我们打开Visual Studio
工具,新建一个可执行控制台项目并配置包含引用目录及库目录,如下图所示;
接着我们来实现拦截并替换弹窗功能,在Windows
中弹窗接口为MessageBoxW
函数,首先需要定义OriginalMessageBoxW
的函数指针,该指针用于指向原始的MessageBoxW
函数地址。接着定义CustomMessageBoxW
函数,在函数内首先将弹窗提示替换为自定义内容,并携带该参数调用OriginalMessageBoxW
原函数地址,以此来实现替代弹窗功能。
#include <iostream>
#include <Windows.h>
#include "detours.h"
#pragma comment(lib, "detours.lib")
// 定义指向原始 MessageBoxW 函数的指针
static int (WINAPI *OriginalMessageBoxW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType) = MessageBoxW;
// 自定义的 MessageBoxW 函数,用于替换原始函数
static int WINAPI CustomMessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType)
{
// 调用原始的 MessageBoxW 函数,但修改了显示的文本
return OriginalMessageBoxW(hWnd, L"hello lyshark", L"MsgBox", MB_OK);
}
接着就是对挂钩与摘钩的函数封装,分别定义这两个函数,其中InstallHook
函数通过Detours
事务的方式,将原始的 MessageBoxW
函数指针替换为自定义的 CustomMessageBoxW
函数指针,从而拦截并修改该函数的行为。相反,RemoveHook
函数则通过类似的事务机制,将自定义的 CustomMessageBoxW
函数指针替换回原始的 MessageBoxW
函数指针,以恢复函数的原始行为。
// 安装钩子
void InstallHook()
{
// 开始一个 Detours 事务
if (DetourTransactionBegin() == NO_ERROR)
{
// 更新当前线程以准备进行钩子操作
if (DetourUpdateThread(GetCurrentThread()) == NO_ERROR)
{
// 将原始函数指针替换为自定义的函数指针
if (DetourAttach(&(PVOID&)OriginalMessageBoxW, CustomMessageBoxW) == NO_ERROR)
{
// 提交事务,完成钩子安装
if (DetourTransactionCommit() == NO_ERROR)
{
printf("钩子已成功安装。\n");
return;
}
}
}
// 如果任何步骤失败,则中止事务
DetourTransactionAbort();
}
printf("安装钩子失败。\n");
}
// 移除钩子
void RemoveHook()
{
// 开始一个 Detours 事务
if (DetourTransactionBegin() == NO_ERROR)
{
// 更新当前线程以准备进行钩子操作
if (DetourUpdateThread(GetCurrentThread()) == NO_ERROR)
{
// 将自定义的函数指针替换回原始函数指针
if (DetourDetach(&(PVOID&)OriginalMessageBoxW, CustomMessageBoxW) == NO_ERROR)
{
// 提交事务,完成钩子移除
if (DetourTransactionCommit() == NO_ERROR)
{
printf("钩子已成功移除。\n");
return;
}
}
}
// 如果任何步骤失败,则中止事务
DetourTransactionAbort();
}
printf("移除钩子失败。\n");
}
在程序入口处,我们分三次调用MessageBoxW
函数,其中第一次调用及最后依次调用均在未挂钩状态下进行,第二次调用之前通过InstallHook()
安装钩子,之后再调用MessageBoxW
函数,并在调用结束后通过RemoveHook()
移除钩子,编译这段代码。
int main(int argc, char *argv[])
{
// 显示原始的消息框
MessageBoxW(NULL, L"hello world", L"MsgBox", MB_OK);
// 安装钩子并显示修改后的消息框
InstallHook();
MessageBoxW(NULL, L"hello world", L"MsgBox", MB_OK);
// 移除钩子并恢复为原始的消息框
RemoveHook();
MessageBoxW(NULL, L"hello world", L"MsgBox", MB_OK);
system("pause");
return 0;
}
使用x64dbg
调试器加载运行代码,并寻找到程序的入口处,由于此处的入口处仅仅是一个main(int argc, char *argv[])
所以,在汇编中我们可以直接寻找三个参数的关键变量位置,找到后即可定位到入口处,此时直接跟进去就可以看到主函数代码;
如下图中所示,当0x00321314
处被执行后则钩子生效,当钩子生效后则底部0x00321347
处的地址将被替换为自定义钩子地址,此时在其之上的入栈操作数将会失效;
继续跟进0x00321347
这个地址,如下图所示该地址中的入口处已被替换为我们自定义的弹窗位置,此处是一个jmp
无条件跳转,预示着将要转向。
我们继续跟进这个转向地址,则可看到如下图所示的反汇编指令集,这里的代码重新入栈了新的字符串变量,并在入栈后调用了原始MessageBoxW
函数,并依次来实现替换函数弹窗中的内容。
此时,当继续调用原始函数时,虽函数中的提示信息为hello world
但由于挂钩生效了则提示信息会被变更为hello lyshark
,以此来实现对函数功能的替换与更正。
在实际应用中,我们通常通过 DLL 注入的方式使用 Detours 库,以便更好地实现对第三方程序的功能替换或修改,例如改变弹窗提示。这种方法能够更高效地应用 Hook 技术,实现对目标程序行为的控制和定制。
例如如下所示的这段代码,当使用注入器将其注入到第三方进程中时,首先DLL_PROCESS_ATTACH
将被执行也就是开始挂钩,在挂钩函数中通过DetourFindFunction
寻找到MessageBoxW
函数的入口地址,并将其存储到OriginalMessageBoxW
指针中,并通过DetourAttach
对其进行挂钩。
#include <windows.h>
#include "detours.h"
#include "detver.h"
#pragma comment(lib, "detours.lib")
// 定义 MessageBoxW 函数指针类型
static int (WINAPI *OriginalMessageBoxW)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) = NULL;
// 自定义的 MessageBoxW 函数
int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
// 可以在这里添加自定义逻辑
return OriginalMessageBoxW(hWnd, L"hello lyshark", lpCaption, uType);
}
// 安装钩子
void InstallHooks()
{
DetourRestoreAfterWith();
// 开始事务
DetourTransactionBegin();
// 更新当前线程
DetourUpdateThread(GetCurrentThread());
// 查找并拦截 MessageBoxW 函数
OriginalMessageBoxW = (int (WINAPI *)(HWND, LPCWSTR, LPCWSTR, UINT))DetourFindFunction("User32.dll", "MessageBoxW");
if (OriginalMessageBoxW != NULL)
{
// 开始挂钩
DetourAttach(&(PVOID&)OriginalMessageBoxW, MyMessageBoxW);
}
// 提交事务
DetourTransactionCommit();
}
// 卸载钩子
void UninstallHooks()
{
// 开始事务
DetourTransactionBegin();
// 更新当前线程
DetourUpdateThread(GetCurrentThread());
// 撤销拦截 MessageBoxW 函数
if (OriginalMessageBoxW != NULL)
{
// 摘除钩子
DetourDetach(&(PVOID&)OriginalMessageBoxW, MyMessageBoxW);
}
// 提交事务
DetourTransactionCommit();
}
// DLL 入口点
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// 禁用线程库调用
DisableThreadLibraryCalls(hModule);
// 安装钩子
InstallHooks();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
// 卸载钩子
UninstallHooks();
break;
}
return TRUE;
}
当挂钩成功后则进程中任何调用弹窗的提示信息都将被替换成hello lyshark
提示,而标题栏因未被替换则依然会保持原始状态,如下图是注入之前与注入之后的提示变化;
至此本章内容结束,其实Hook在安全领域的应用相当广泛,例如可以监控和拦截指定的API调用,检测分析程序的行为,拦截网络通信函数,监控数据传输,拦截文件操作和注册表访问等等,本文也只是抛砖引玉让读者能认识Detours
库。更多有用的案例可自行参考samples
目录下的内容学习。