微软Detours Hook库编译与使用

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

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 命令执行编译。

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

目录
相关文章
|
4月前
|
定位技术 数据处理 C++
Visual Studio软件调用已经配置、编译好的C++第三方库的方法
Visual Studio软件调用已经配置、编译好的C++第三方库的方法
132 1
|
JavaScript 前端开发 Linux
Hook神器—Frida安装
Hook神器—Frida安装
|
算法 Java 数据安全/隐私保护
frida hook native层巧解Android逆向题
frida hook native层巧解Android逆向题
|
编译器 Linux C语言
【C 语言】动态库封装与设计 ( Windows 动态库简介 | Visual Studio 调用动态库 )
【C 语言】动态库封装与设计 ( Windows 动态库简介 | Visual Studio 调用动态库 )
263 0
【C 语言】动态库封装与设计 ( Windows 动态库简介 | Visual Studio 调用动态库 )
关于 国产麒麟系统编译Qt项目是报错:error: cannot find -lGL 的解决方法
关于 国产麒麟系统编译Qt项目是报错:error: cannot find -lGL 的解决方法
关于 国产麒麟系统编译Qt项目是报错:error: cannot find -lGL 的解决方法
|
Ubuntu Linux Windows
关于 QWidget+Qml程序打包到ubuntu时,程序与系统库版本不同,编译时添加并依赖自带库 的方法
关于 QWidget+Qml程序打包到ubuntu时,程序与系统库版本不同,编译时添加并依赖自带库 的方法
关于 QWidget+Qml程序打包到ubuntu时,程序与系统库版本不同,编译时添加并依赖自带库 的方法
|
程序员 开发工具 iOS开发
iOS底层原理:苹果开源 objc4-818 源码项目的编译和调试(三)
作为一名iOS程序员,探索OC底层原理永不止息,同时也是永远的痛,最开始只能靠猜测!后面慢慢找到了苹果官方开源的源码来辅助看一下,但是尽管这样,还是显得不太直观!如果objc源码能够像我们自己创建的项目一样直接编译调试,像我们自己的代码一样能够直接 LLDB 调试,流程跟踪,那简直不要太爽。废话不多说,开炮~ 哦,不是,是开干~!🚀
iOS底层原理:苹果开源 objc4-818 源码项目的编译和调试(三)
|
Unix 程序员 iOS开发
iOS底层原理:苹果开源 objc4-818 源码项目的编译和调试(二)
作为一名iOS程序员,探索OC底层原理永不止息,同时也是永远的痛,最开始只能靠猜测!后面慢慢找到了苹果官方开源的源码来辅助看一下,但是尽管这样,还是显得不太直观!如果objc源码能够像我们自己创建的项目一样直接编译调试,像我们自己的代码一样能够直接 LLDB 调试,流程跟踪,那简直不要太爽。废话不多说,开炮~ 哦,不是,是开干~!🚀
iOS底层原理:苹果开源 objc4-818 源码项目的编译和调试(二)
|
SQL 物联网 Linux
跨平台开发--C# 使用C/C++生成的动态链接库
跨平台开发--C# 使用C/C++生成的动态链接库
368 0
跨平台开发--C# 使用C/C++生成的动态链接库
|
IDE 开发工具
Visual Studio下程序开发: 引用库出现 error LNK2026: 模块对于 SAFESEH 映像是不安全的。
Visual Studio下程序开发: 引用库出现 error LNK2026: 模块对于 SAFESEH 映像是不安全的。
130 0
Visual Studio下程序开发: 引用库出现 error LNK2026: 模块对于 SAFESEH 映像是不安全的。