[C++调试技巧] MinHook 介绍|文档|例子


一.基础

1.MinHook的简介

MinHook是一个基于微软Detours的一个可移植Hook库,它使用了内存污染和跳转技术来实现Hook.
通过使用MinHook,你可以在不需要修改原来函数代码的情况下,运行时更改函数定义.
这对于调试,测试,以及在其他程序中注入自己的代码都非常有用.

使用MinHook来挂钩函数,即使这些函数是由编译器优化过的,MinHook也可以在不修改原来代码的情况下Hook.
这使得MinHook特别适合用于对受保护函数进行Hook

但是它也有一些限制
当然,MinHook毕竟是开源项目,你只需要稍微处理一下,便可以保证兼容与稳定性.

MinHook下载地址:https://github.com/TsudaKageyu/minhook

2.MinHook的函数

常用函数


初始化MinHook库

MH_STATUS WINAPI MH_Initialize(VOID);

初始化MinHook库.你必须在一切Hook开始前恰好调用一次此函数


释放MinHook库

MH_STATUS  WINAPI MH_Uninitialize(VOID);

释放MinHook库.你必须在一切Hook结束后恰好调用一次此函数


创建用于指定目标函数的钩子

MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);

创建用于指定目标函数的钩子,创建完毕后是禁用状态
参数:
pTarget [in] 指向将被转向函数覆盖的目标函数的指针。
pDetour [in] 指向将覆盖目标函数的转向函数的指针。
ppOriginal [out] 指向用于调用原始目标函数的跳板函数指针。
此参数可以为 NULL。


创建用于指定API函数的钩子

MH_STATUS WINAPI MH_CreateHookApi(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal);

创建用于指定API函数的钩子,创建完毕后是禁用状态
参数:
pszModule [in] 指向包含目标函数的已加载模块名称的指针。
pszTarget [in] 指向将被转向函数覆盖的目标函数名称的指针。
pDetour [in] 指向将覆盖目标函数的转向函数的指针。
ppOriginal [out] 指向用于调用原始目标函数的跳板函数的指针。
此参数可以为 NULL。

MH_STATUS WINAPI MH_CreateHookApiEx(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget);

创建指定的API函数的钩子,处于禁用状态。
参数:
pszModule [in] 指向包含目标函数的已加载模块名称的指针。
pszTarget [in] 指向将被转向函数覆盖的目标函数名称的指针。
pDetour [in] 指向将覆盖目标函数的转向函数的指针。
ppOriginal [out] 指向用于调用原始目标函数的跳板函数的指针。
此参数可以为 NULL。
ppTarget [out] 指向将与其他函数一起使用的目标函数的指针。
此参数可以为 NULL。


移除以创建的钩子

MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);

参数:
pTarget [in] 指向目标函数的指针。


启用以创建的钩子

MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);

参数:
pTarget [in] 指向目标函数的指针。
如果此参数为 MH_ALL_HOOKS,则所有已创建的钩子将同时启用。


禁用以创建的钩子

MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);

参数:
pTarget [in] 指向目标函数的指针。
如果此参数是 MH_ALL_HOOKS,则所有创建的钩子
将在一次调用中禁用。


其他函数

用队列来启用钩子

MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);

参数:
pTarget [in] 指向目标函数的指针。
如果该参数为 MH_ALL_HOOKS,则所有已创建的钩子都将排队等待启用。


用队列来禁用钩子

MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);

参数:
pTarget [in] 指向目标函数的指针。
/如果此参数为MH_ALL_HOOKS,则所有已创建的钩子都将被排队禁用。


应用所有排队的更改

MH_STATUS WINAPI MH_ApplyQueued(VOID);

一次性应用所有排队的更改


将MH_STATUS转换为字符串

const char * WINAPI MH_StatusToString(MH_STATUS status);

将MH_STATUS枚举类型转换为字符串


3.MinHook的枚举 MH_STATUS

MH_STATUS是个枚举类型,用于记录MinHook的状态 其值为:

  • MH_UNKNOWN = -1, // 未知错误,不应该出现
  • MH_OK = 0, // 成功
  • MH_ERROR_ALREADY_INITIALIZED, // 已经初始化
  • MH_ERROR_NOT_INITIALIZED, // 未初始化,或者已经释放
  • MH_ERROR_ALREADY_CREATED, // 指定目标函数已经被Hook
  • MH_ERROR_NOT_CREATED, // 指定目标函数未被Hook
  • MH_ERROR_ENABLED, // 指定目标函数已经被启用
  • MH_ERROR_DISABLED, // 指定目标函数已经被禁用
  • MH_ERROR_NOT_EXECUTABLE, // 指定的指针无效,指向未分配或者不可
  • MH_ERROR_UNSUPPORTED_FUNCTION, // 指定的目标函数不支持Hook
  • MH_ERROR_MEMORY_ALLOC, // 内存分配失败
  • MH_ERROR_MEMORY_PROTECT, // 内存保护修改失败
  • MH_ERROR_MODULE_NOT_FOUND, // 指定的模块未加载
  • MH_ERROR_FUNCTION_NOT_FOUND, // 指定的函数未找到

4.MinHook的限制

MinHook目前的限制如下:

  • 不能Hook跨DLL边界的函数,如果你需要Hook跨DLL边界的函数,你可以尝试使用EasyHook
  • 不能Hook导出函数.如果你需要Hook导出函数,你可以尝试使用EasyHook
  • 不能在64位应用程序中Hook内存地址为绝对地址的函数,在64位程序中,内存地址位绝对地址的函数时很少见的,因此这并不是很常见的限制
  • 不能在当前版本的.Net中Hook任何函数,在.Net中以不修改为目的Hook函数很困难,因为函数可以被JIT编译,并且函数的调用方式也可能是动态的,MinHook目前不支持在.Net中Hook函数,但是在未来可能会支持.
  • MinHook不能在UWP应用程序中使用,UWP应用程序运行在受保护的容器中,因此无法使用MinHook
  • MinHook也不能在Windows Store应用商店下载的程序中使用,他们同样运行于受保护容器(微软商店只有UWP程序,但UWP程序不只在微软商店可以安装,所以单独列出来)
  • 在使用MinHook进行Hook时,可能会出现原函数的调用时间变长.

二.MinHook的例子

1.简单例子

这是一个使用MinHook的简单例子
该例子编码标准为C++20,但没有运用任何新特性,所以应该可以兼容大部分

#include <Windows.h>
#include <MinHook.h>
#pragma comment(lib, "libMinHook-x86-v141-mtd.lib")

// 定义Hook函数原型
int __stdcall HookMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
int __stdcall HookMessageBeep(UINT uType);

int main(int, char**)
{
    /*
//     MH_STATUS是个枚举类型,用于记录MinHook的状态
//     其值为:
//     MH_UNKNOWN = -1,    // 未知错误,不应该出现
//     MH_OK = 0,          // 成功
//     MH_ERROR_ALREADY_INITIALIZED,   // 已经初始化
//     MH_ERROR_NOT_INITIALIZED,       // 未初始化,或者已经释放
//     MH_ERROR_ALREADY_CREATED,       // 指定目标函数已经被Hook
//     MH_ERROR_NOT_CREATED,           // 指定目标函数未被Hook
//     MH_ERROR_ENABLED,               // 指定目标函数已经被启用
//     MH_ERROR_DISABLED,              // 指定目标函数已经被禁用
//     MH_ERROR_NOT_EXECUTABLE,        // 指定的指针无效,指向未分配或者不可执行的内存
//     MH_ERROR_UNSUPPORTED_FUNCTION,  // 指定的目标函数不支持Hook
//     MH_ERROR_MEMORY_ALLOC,          // 内存分配失败
//     MH_ERROR_MEMORY_PROTECT,        // 内存保护修改失败
//     MH_ERROR_MODULE_NOT_FOUND,      // 指定的模块未加载
//     MH_ERROR_FUNCTION_NOT_FOUND,    // 指定的函数未找到
     */
    MH_STATUS status = MH_Initialize(); // 初始化MinHook
    if (status != MH_OK) // 如果初始化失败
    {
        MessageBoxA(nullptr, "MinHook初始化失败", "错误", MB_OK); // 弹出错误提示
        return 0; // 退出程序
    }

    // Hook MessageBoxA函数
    status = MH_CreateHookApi(L"user32.dll", "MessageBoxA", reinterpret_cast<LPVOID>(HookMessageBoxA), nullptr);
    if (status != MH_OK) // 如果Hook失败
    {
        MessageBoxA(nullptr, "Hook MessageBoxA失败", "错误", MB_OK); // 弹出错误提示
        return 0; // 退出程序
    }

    // Hook MessageBeep函数
    status = MH_CreateHookApi(L"user32.dll", "MessageBeep", reinterpret_cast<LPVOID>(HookMessageBeep), nullptr);
    if (status != MH_OK) // 如果Hook失败
    {
        MessageBoxA(nullptr, "Hook MessageBeep失败", "错误", MB_OK); // 弹出错误提示
        return 0; // 退出程序
    }

    // 启用Hook
    // MH_ALL_HOOKS表示启用所有Hook
    status = MH_EnableHook(MH_ALL_HOOKS);
    if (status != MH_OK) // 如果启用Hook失败
    {
        MessageBoxA(nullptr, "启用Hook失败", "错误", MB_OK); // 弹出错误提示
        return 0; // 退出程序
    }

    // 弹出一个MessageBox
    // 如果Hook成功,它的文本应该是"Hooked!"
    MessageBoxA(nullptr, "Hello World!", "Hello", MB_OK);
    MessageBeep(MB_ICONASTERISK); // 播放一个提示音

    // MessageBoxA和MessageBeep的Hook已经因为执行一次,在Hook函数内被禁用了
    // 所以这里再次执行它们,它们的行为应该和未Hook时一样
    MessageBoxA(nullptr, "Hello World!", "Hello", MB_OK);
    MessageBeep(MB_ICONASTERISK);

    MH_EnableHook(reinterpret_cast<LPVOID>(HookMessageBoxA)); // 启用MessageBoxA的Hook
    // 释放MessageBoxA的Hook
    MH_RemoveHook(reinterpret_cast<LPVOID>(MessageBoxA));
    // 弹出一个MessageBox
    // 如果Hook未被释放,它的文本应该是"Hooked!"
    MessageBoxA(nullptr, "Hello World!", "Hello", MB_OK);

    // 释放MinHook
    MH_Uninitialize();
    return 0;
}

// 定义Hook函数
int __stdcall HookMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
    // 关闭Hook 否则会出现无限递归
    // 即MessageBoxA调用HookMessageBoxA,HookMessageBoxA又调用MessageBoxA
    if (MH_DisableHook(reinterpret_cast<LPVOID>(MessageBoxA)) != MH_OK)
        return 1;
    return MessageBoxA(hWnd, "Hooked!", lpCaption, uType);
}

int __stdcall HookMessageBeep(UINT uType)
{
    MessageBoxA(nullptr, "BeepHooked!", "Hello", MB_OK);
    // 关闭Hook 否则会出现无限递归
    // 即MessageBeep调用HookMessageBeep,HookMessageBeep又调用MessageBeep
    _exit:
    if (MH_DisableHook(reinterpret_cast<LPVOID>(MessageBeep)) != MH_OK)
        return 1;
    return MessageBeep(uType);
}


2.HookDLL

靶子程序源码

#include <iostream>
#include <format>
#include <Windows.h>

void Test(int a, int b)
{
    Sleep(1000);
    std::cout << std::format("Test() called from {}:{}", __FUNCTION__, a + b) << std::endl;
    // 打印自身函数地址 16进制
    std::cout << std::format("Test() called from {}:{}", __FUNCTION__, reinterpret_cast<void*>(Test)) << std::endl;
}

int main(int, char**)
{
    std::ios::sync_with_stdio(false);                   // 解除C++流和C流的绑定
    std::cin.tie(nullptr);                              // cin缓冲区关联到cout缓冲区

    while (true)                                                // 持续循环
        Test(1,5);                                              // 调用Test函数

    return 0;
}

DLL源码

#include <iostream>
#include <format>

#include <MinHook.h>
#pragma comment(lib, "libMinHook-x86-v141-mtd.lib")

// 函数指针定义
using TestFunc = void(*)(int, int);

// 函数:TestHook
// 说明:测试函数
// 参数: a - 参数1 | b - 参数2
// 返回值:无
void TestHook(int a, int b)
{
    Sleep(1000);
    std::cout << "------------------" << std::endl;
    std::cout << std::format("TestHook() called from {}:{}", __FUNCTION__, a + b) << std::endl;
    std::cout << "------------------" << std::endl;
}

// 函数:执行
void Execute()
{
    std::cout << std::format("DLL Injected: {}", __FUNCTION__) << std::endl;

    // 初始化MinHook
    MH_Initialize();
    // 获取Test函数地址       0x9A1EAB
    auto pTest = reinterpret_cast<TestFunc>(0x9a1eab);
    if (pTest)
        if (MH_CreateHook(reinterpret_cast<LPVOID>(pTest), reinterpret_cast<LPVOID>(TestHook), nullptr) == MH_OK)
            std::cout << std::format("MH_CreateHook() called from {}:{}", __FUNCTION__, "OK") << std::endl;

    while (true)
    {
        // 检测按键Insert,按下则启用钩子
        if (GetAsyncKeyState(VK_INSERT) & 1)
        {
            MH_EnableHook(reinterpret_cast<LPVOID>(pTest));
            std::cout << std::format("Hook Enabled: {}", __FUNCTION__) << std::endl;
        }
        else if (GetAsyncKeyState(VK_DELETE) & 1)
        {
            MH_DisableHook(reinterpret_cast<LPVOID>(pTest));
            std::cout << std::format("Hook Disabled: {}", __FUNCTION__) << std::endl;
        }
        // 检测按键End,按下则剥离DLL
        else if (GetAsyncKeyState(VK_END) & 1)
        {
            MH_Uninitialize();
            std::cout << std::format("DLL Unloaded: {}", __FUNCTION__) << std::endl;
            FreeLibraryAndExitThread(reinterpret_cast<HMODULE>(GetModuleHandleA("Hook.dll")), 0);
        }
    }
}

// DLL 入口
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        Execute();
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

1.运行

这是靶子程序运行的效果.
HookTest.exe


DLL注入成功打印的字符串
“DLL Injected:Execute”
Hook 创建成功的字符串
“MH_CreateHook() called from Execute:OK”
Injected
根据DLL代码,我们按下insert键,将会启用关于Test函数的Hook
EnableHook
按下Delete键,我们将会关闭关于Test函数的Hook
DisableHook
如果我们卸载掉DLL,将会执行MinHook的MH_Uninitialize()函数
它会自动为我们还原之前的状态
FreeLibrary

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八宝咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值