微软Detours Hook库编译与使用

Detours 是微软开发的一个强大的Windows API钩子库,用于监视和拦截函数调用。它广泛应用于微软产品团队和众多独立软件开发中,旨在无需修改原始代码的情况下实现函数拦截和修改。Detours 在调试、监控、日志记录和性能分析等方面表现出色,已成为开发者的重要工具。本章将指导读者编译并使用Detours库,通过实现一个简单的弹窗替换功能,帮助读者熟悉该库的使用技巧。

Detours 是一个兼容多个Windows系列操作系统版本(包括 Windows XPWindows 11)的工具库。它现在在 MIT 开源许可证下发布,简化了开发者的使用许可流程,并允许社区利用开源工具和流程来支持其发展,目前该库的稳定版本为4.0.1读者可通过如下官方链接自行下载到本地。

下载文件后打开目录,其中src目录下存储的是Detours库的源代码,而samples则是一些使用案例,当准备就绪后读者需要打开Visual Studio开发者命令提示符,你可以从开始菜单中找到Visual Studio Tools工具菜单,并在其中找到VS20XX x86 本机工具命令提示Developer Command Prompt for VS 20XX字样,此处以x86为例,在命令提示符中跳转到Detours源代码目录,运行 nmake 命令执行编译。

20240814145001

如果一切顺利,这将会编译Detours库并生成所需的二进制文件,其中include保存有头文件信息,而lib.X86则包含有detours.lib库文件,如下图中所示。

20240814145214

接着我们打开Visual Studio工具,新建一个可执行控制台项目并配置包含引用目录及库目录,如下图所示;

20240814150752

接着我们来实现拦截并替换弹窗功能,在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[])所以,在汇编中我们可以直接寻找三个参数的关键变量位置,找到后即可定位到入口处,此时直接跟进去就可以看到主函数代码;

20240814160125

如下图中所示,当0x00321314处被执行后则钩子生效,当钩子生效后则底部0x00321347处的地址将被替换为自定义钩子地址,此时在其之上的入栈操作数将会失效;

20240814160435

继续跟进0x00321347这个地址,如下图所示该地址中的入口处已被替换为我们自定义的弹窗位置,此处是一个jmp无条件跳转,预示着将要转向。

20240814160808

我们继续跟进这个转向地址,则可看到如下图所示的反汇编指令集,这里的代码重新入栈了新的字符串变量,并在入栈后调用了原始MessageBoxW函数,并依次来实现替换函数弹窗中的内容。

20240814160910

此时,当继续调用原始函数时,虽函数中的提示信息为hello world但由于挂钩生效了则提示信息会被变更为hello lyshark,以此来实现对函数功能的替换与更正。

20240814161232

在实际应用中,我们通常通过 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提示,而标题栏因未被替换则依然会保持原始状态,如下图是注入之前与注入之后的提示变化;

20240814162944

至此本章内容结束,其实Hook在安全领域的应用相当广泛,例如可以监控和拦截指定的API调用,检测分析程序的行为,拦截网络通信函数,监控数据传输,拦截文件操作和注册表访问等等,本文也只是抛砖引玉让读者能认识Detours库。更多有用的案例可自行参考samples目录下的内容学习。