目录

1.简介

2.安装与集成

3.MinHook 核心 API 详解

4.MinHook 底层核心原理

5.MinHook 基本使用流程

6.完整可运行示例:钩子 MessageBoxA

7.MinHook 进阶用法与场景

8.MinHook 使用注意事项

9.总结

拓展学习


1.简介

        MinHook 是由 Tsuda Kageyu 开发的开源库(MIT 协议),仅支持x86(32 位)和 x64(64 位)Windows,核心目标是让开发者以极简的代码实现稳定的内联钩子,无需手动处理内存权限、指令跳转、地址计算等底层细节。

        核心优势:

  • 轻量级:源码仅千行级,预编译库体积极小(静态库 < 100KB),无任何第三方依赖;
  • 跨架构:完美支持 x86/x64,自动处理两种架构的指令差异和地址计算;
  • 易用性:提供简洁的 C 语言 API,固定的使用流程,新手半小时即可上手;
  • 稳定性:自动处理内存权限修改、指令对齐、ASLR(地址空间布局随机化)等问题,底层做了大量兼容性处理;
  • 线程安全:核心 API 的原子操作设计,多线程环境下使用无额外风险;
  • 低侵入性:钩子移除后可完全恢复目标函数的原始代码,无残留。

        使用场景:

  • 拦截 Windows 系统 API(如MessageBoxACreateFileAWSASend等);
  • 调试 / 逆向中监控目标进程的函数调用(参数、返回值、调用栈);
  • 软件功能扩展(在原函数执行前 / 后插入自定义逻辑);
  • 游戏修改(钩子游戏内的渲染、输入、网络函数);
  • 钩子自定义动态库(DLL)或进程内的本地函数。

2.安装与集成

下载地址:https://gitcode.com/gh_mirrors/mi/minhook

1.源码编译(适合定制化 / 最新功能)

  • 克隆源码git clone https://gitcode.com/gh_mirrors/mi/minhook.git
  • 生成工程:源码根目录有CMakeLists.txt,用 CMake 生成 Visual Studio 工程(选择对应架构);
  • 编译工程:用 VS 打开生成的工程,编译minhook项目,会在build/lib下生成 x86/x64 的库文件;
  • 后续配置:和方式 1 一致,将编译后的includelib配置到自己的项目中。

2.vcpkg安装

vcpkg install minhook

3.MinHook 核心 API 详解

MinHook 的所有 API 都定义在MinHook.h中,返回值均为MH_STATUS枚举(表示操作结果),核心 API 仅 10 个左右,分为初始化 / 卸载、钩子操作、辅助调试三类,以下是最常用的核心 API 详解。

1.基础类型:MH_STATUS(操作结果枚举)

所有 API 的返回值,核心取值(完整取值见MinHook.h):

typedef enum _MH_STATUS
{
    MH_OK = 0,                  // 操作成功
    MH_ERROR_ALREADY_INITIALIZED, // MinHook已初始化
    MH_ERROR_NOT_INITIALIZED,     // MinHook未初始化
    MH_ERROR_ALREADY_CREATED,     // 钩子已创建
    MH_ERROR_NOT_CREATED,         // 钩子未创建
    MH_ERROR_ALREADY_ENABLED,     // 钩子已启用
    MH_ERROR_NOT_ENABLED,         // 钩子未启用
    MH_ERROR_MEMORY_ALLOCATION,   // 内存分配失败
    MH_ERROR_MEMORY_PROTECTION,   // 内存权限修改失败
    MH_ERROR_INVALID_PARAMETER,   // 无效参数
    MH_ERROR_UNSUPPORTED_FUNCTION // 不支持的函数(如指令特殊)
} MH_STATUS;

调试技巧:使用MH_StatusToString将错误码转为字符串,快速定位问题:

// 将MH_STATUS转为错误描述字符串
const char* errMsg = MH_StatusToString(status);
printf("错误:%s\n", errMsg);

2.初始化 / 卸载 API(全局仅需调用一次)

1)MH_Initialize

MH_STATUS MH_CDECL MH_Initialize(void);
  • 功能:初始化 MinHook 的全局资源(如内存池、线程同步对象);
  • 注意:进程内仅需调用一次,多次调用会返回MH_ERROR_ALREADY_INITIALIZED

2)MH_Uninitialize

MH_STATUS MH_CDECL MH_Uninitialize(void);
  • 功能:卸载 MinHook,释放所有全局资源,自动禁用并移除所有已创建的钩子;
  • 注意:程序退出前必须调用,否则可能导致内存泄漏或进程崩溃。

3.钩子核心操作 API(最常用)

1)MH_CreateHook(核心:创建钩子)

MH_STATUS MH_CDECL MH_CreateHook(
    LPVOID pTarget,       // 目标函数的真实地址(FARPROC/LPVOID)
    LPVOID pDetour,       // 钩子函数的地址(自定义的函数)
    LPVOID* ppOriginal    // 跳板函数的指针地址(MinHook自动初始化)
);
  • 核心作用:绑定「目标函数 - 钩子函数 - 跳板函数」,MinHook 会在此步骤创建跳板函数;
  • 注意:ppOriginal必须是函数指针的地址(即&跳板函数名),否则会导致内存错误。

2)MH_EnableHook(启用钩子)

MH_STATUS MH_CDECL MH_EnableHook(LPVOID pTarget);
  • 功能:禁用指定的钩子,禁用后目标函数恢复原始行为;
  • 特殊参数:传入MH_ALL_HOOKS,可禁用所有已启用的钩子
  • 注意:未启用钩子时调用,返回MH_ERROR_NOT_ENABLED

3)MH_DisableHook(禁用钩子)

MH_STATUS MH_CDECL MH_DisableHook(LPVOID pTarget);
  • 功能:禁用指定的钩子,禁用后目标函数恢复原始行为;
  • 特殊参数:传入MH_ALL_HOOKS,可禁用所有已启用的钩子
  • 注意:未启用钩子时调用,返回MH_ERROR_NOT_ENABLED

4)MH_RemoveHook(移除钩子)

MH_STATUS MH_CDECL MH_RemoveHook(LPVOID pTarget);
  • 功能:移除已创建的钩子,释放跳板函数的内存,钩子被移除后无法再启用(需重新调用MH_CreateHook);
  • 注意:移除前需先禁用钩子,否则返回错误。

4.辅助调试 API

1)MH_StatusToString

const char* MH_CDECL MH_StatusToString(MH_STATUS status);
  • 功能:将MH_STATUS错误码转为人类可读的字符串(如MH_ERROR_ALREADY_INITIALIZED→"Already initialized");
  • 适用场景:调试时快速定位 API 调用失败的原因。

2)MH_GetHookInfo

MH_STATUS MH_CDECL MH_GetHookInfo(
    LPVOID pTarget,
    LPMH_HOOK_INFO pHookInfo
);
  • 功能:获取指定钩子的详细信息(目标函数地址、钩子函数地址、跳板函数地址、钩子状态);
  • 适用场景:高级调试,验证钩子的绑定状态。

4.MinHook 底层核心原理

MinHook 的核心是内联钩子(Inline Hook),这是 Windows 平台最常用的钩子技术,区别于消息钩子、API 钩子(IAT/EAT),内联钩子直接修改目标函数的机器码,实现函数重定向,兼容性和通用性更强。

1.内联钩子的核心思想

简单来说:修改目标函数的前几个字节,替换为「远跳指令」,跳转到自定义的钩子函数;当需要调用原函数时,通过「跳板函数(Trampoline)」执行原始逻辑

2.MinHook 的具体实现步骤(分 x86/x64)

1)x86(32 位)架构

  • 目标函数的前5 个字节是 x86 远跳指令(JMP DWORD PTR [addr])的固定长度,MinHook 先保存这 5 个原始字节
  • 修改目标函数的内存权限为PAGE_EXECUTE_READWRITE(默认代码段是只读可执行,无法修改);
  • 将目标函数前 5 字节替换为远跳指令,跳转到开发者定义的钩子函数
  • 创建跳板函数(Trampoline):将保存的 5 个原始字节复制到跳板函数,再在跳板函数末尾添加跳回「目标函数第 6 字节」的指令,实现原函数的完整执行。

2)x64(64 位)架构

x64 的远跳指令长度不是固定 5 字节(因地址是 64 位),MinHook 会自动计算需要修改的前 14 个字节,其余逻辑和 x86 一致:

  • 保存目标函数前 14 个原始字节;
  • 替换为 x64 的远跳指令,跳转到钩子函数;
  • 跳板函数包含原始 14 字节 + 跳回目标函数第 15 字节的指令。

3.关键概念:跳板函数(Trampoline)

跳板函数是 MinHook 自动创建的「原函数代理」,是连接钩子函数和原函数的关键,核心作用:

  • 钩子函数中如果需要调用原始的目标函数必须通过跳板函数,不能直接调用目标函数(否则会触发远跳,无限递归调用钩子函数);
  • 跳板函数的函数签名、调用约定必须和目标函数、钩子函数完全一致;
  • MinHook 会在创建钩子时自动初始化跳板函数,销毁钩子时自动释放其内存。

4.MinHook 的封装层

开发者使用时无需处理:内存权限修改、指令跳转计算、跳板函数创建 / 释放、ASLR 地址重定位、线程安全同步,这些都由 MinHook 的底层 API 自动完成。

5.MinHook 基本使用流程

MinHook 的使用流程完全固定,无论钩子什么函数,都遵循「初始化→创建钩子→启用钩子→业务逻辑→禁用钩子→卸载」6 个步骤,核心是保证函数签名、调用约定一致

核心前置要求

钩子函数、跳板函数、目标函数必须满足:

  1. 函数签名完全一致(返回值类型、参数列表完全相同);
  2. 调用约定完全一致(关键!x86 下常见stdcall/cdecl/thiscall,x64 下 Windows 统一fastcall,MinHook 自动适配,但开发者需显式指定);
  3. 跳板函数为函数指针,由 MinHook 在MH_CreateHook中自动初始化。

固定使用模板(伪代码)

// 1. 包含MinHook头文件
#include <MinHook.h>
// 链接MinHook库(也可在VS属性中配置)
#pragma comment(lib, "minhook.lib")

// 2. 定义目标函数的函数指针类型(核心:统一签名和调用约定)
typedef 原函数返回值类型 (WINAPI* 函数指针别名)(原函数参数列表);
// 3. 声明跳板函数(MinHook自动初始化,用于调用原函数)
函数指针别名 跳板函数名 = NULL;

// 4. 定义钩子函数(签名、调用约定和目标函数完全一致,自定义逻辑)
原函数返回值类型 WINAPI 钩子函数名(原函数参数列表)
{
    // 自定义前置逻辑(如拦截参数、修改参数)
    ...
    // 调用原函数:必须通过跳板函数,不能直接调用目标函数!
    原函数返回值类型 ret = 跳板函数名(参数列表);
    // 自定义后置逻辑(如拦截返回值、修改返回值)
    ...
    return ret;
}

int main()
{
    // 步骤1:初始化MinHook(全局仅需调用一次)
    MH_STATUS status = MH_Initialize();
    if (status != MH_OK) { /* 初始化失败,处理错误 */ }

    // 步骤2:获取目标函数的真实地址(如从DLL中获取)
    FARPROC targetFunc = GetProcAddress(LoadLibraryA("目标DLL名.dll"), "目标函数名");
    if (targetFunc == NULL) { /* 获取地址失败,处理错误 */ }

    // 步骤3:创建钩子(绑定目标函数、钩子函数、跳板函数)
    status = MH_CreateHook((LPVOID)targetFunc, (LPVOID)钩子函数名, (LPVOID*)&跳板函数名);
    if (status != MH_OK) { /* 创建钩子失败,处理错误 */ }

    // 步骤4:启用钩子(启用后,调用目标函数会触发钩子函数)
    status = MH_EnableHook((LPVOID)targetFunc);
    if (status != MH_OK) { /* 启用钩子失败,处理错误 */ }

    // 业务逻辑:此时调用目标函数,会执行钩子函数的逻辑
    ...

    // 步骤5:禁用钩子(可选,禁用后恢复原函数行为)
    status = MH_DisableHook((LPVOID)targetFunc);
    // 可选:移除钩子(释放跳板函数内存,无需再使用时调用)
    // status = MH_RemoveHook((LPVOID)targetFunc);

    // 步骤6:卸载MinHook(程序退出前调用,释放全局资源)
    MH_Uninitialize();

    return 0;
}

6.完整可运行示例:钩子 MessageBoxA

以钩子 Windows 系统 API user32.dll中的MessageBoxA(弹窗函数)为例,实现修改弹窗标题和内容的功能,该示例可直接在 VS 中编译运行(x86/x64 均可,只需对应配置库文件)。

#include <Windows.h>
#include <MinHook.h>
#include <cstdio>

// 链接MinHook静态库(x86/x64对应各自的lib)
#pragma comment(lib, "minhook.lib")

// 1. 定义MessageBoxA的函数指针类型(WINAPI=stdcall,x86下必须,x64下兼容)
typedef int (WINAPI* pMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);
// 2. 声明跳板函数(MinHook自动初始化,调用原MessageBoxA)
pMessageBoxA oMessageBoxA = NULL;

// 3. 定义钩子函数(签名、调用约定和原函数完全一致)
int WINAPI hkMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
    // 自定义逻辑:修改弹窗内容和标题
    const char* newText = "MinHook钩子成功!原内容:" + (std::string)lpText;
    const char* newCaption = "MinHook示例";
    // 调用原函数:通过跳板函数oMessageBoxA
    int ret = oMessageBoxA(hWnd, newText, newCaption, uType);
    // 后置逻辑:打印弹窗的返回值(IDOK=1,IDCANCEL=2)
    printf("弹窗返回值:%d\n", ret);
    return ret;
}

int main()
{
    // 步骤1:初始化MinHook
    MH_STATUS status = MH_Initialize();
    if (status != MH_OK)
    {
        printf("MinHook初始化失败:%s\n", MH_StatusToString(status));
        return -1;
    }
    printf("MinHook初始化成功\n");

    // 步骤2:获取MessageBoxA的真实地址(从user32.dll中获取)
    LPVOID targetFunc = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
    if (targetFunc == NULL)
    {
        printf("获取MessageBoxA地址失败:%d\n", GetLastError());
        MH_Uninitialize();
        return -1;
    }
    printf("获取MessageBoxA地址成功:0x%p\n", targetFunc);

    // 步骤3:创建钩子
    status = MH_CreateHook(targetFunc, (LPVOID)hkMessageBoxA, (LPVOID*)&oMessageBoxA);
    if (status != MH_OK)
    {
        printf("创建钩子失败:%s\n", MH_StatusToString(status));
        MH_Uninitialize();
        return -1;
    }
    printf("创建钩子成功\n");

    // 步骤4:启用钩子
    status = MH_EnableHook(targetFunc);
    if (status != MH_OK)
    {
        printf("启用钩子失败:%s\n", MH_StatusToString(status));
        MH_RemoveHook(targetFunc);
        MH_Uninitialize();
        return -1;
    }
    printf("启用钩子成功,调用MessageBoxA测试...\n");

    // 业务逻辑:调用MessageBoxA,会触发钩子函数
    MessageBoxA(NULL, "原始测试内容", "原始标题", MB_OKCANCEL);

    // 步骤5:禁用钩子
    status = MH_DisableHook(targetFunc);
    if (status != MH_OK)
    {
        printf("禁用钩子失败:%s\n", MH_StatusToString(status));
    }
    else
    {
        printf("禁用钩子成功,再次调用MessageBoxA测试原功能...\n");
        MessageBoxA(NULL, "原始测试内容", "原始标题", MB_OKCANCEL);
    }

    // 步骤6:卸载MinHook
    MH_Uninitialize();
    printf("MinHook卸载成功,程序退出\n");

    system("pause");
    return 0;
}

运行效果:

  • 程序启动后,首次调用MessageBoxA会弹出修改后的标题和内容,控制台打印弹窗返回值;
  • 禁用钩子后,再次调用MessageBoxA会恢复原始的标题和内容
  • 全程无崩溃,钩子移除后无残留。

7.MinHook 进阶用法与场景

1.钩子类成员函数

C++ 类成员函数的本质是this指针的普通函数,钩子时需注意:

  • 成员函数的调用约定:x86 下默认thiscallthis指针通过 ECX 寄存器传递),x64 下仍为fastcall
  • 获取成员函数的真实地址:直接取类成员函数的地址(如&Class::Func),或通过虚函数表获取虚函数地址;
  • 钩子函数的第一个参数必须是Class* this(显式声明),其余参数和原成员函数一致。

2.批量钩子多个函数

如果需要钩子多个 API,可将「目标函数地址、钩子函数、跳板函数」封装为结构体数组,通过循环实现批量创建、启用钩子:

// 定义钩子结构体
typedef struct
{
    LPVOID target;  // 目标函数地址
    LPVOID detour;  // 钩子函数地址
    LPVOID* original;// 跳板函数地址
    const char* name;// 函数名(调试用)
} HOOK_ENTRY;

// 定义钩子数组
HOOK_ENTRY hooks[] = {
    {GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA"), (LPVOID)hkMessageBoxA, (LPVOID*)&oMessageBoxA, "MessageBoxA"},
    {GetProcAddress(LoadLibraryA("kernel32.dll"), "CreateFileA"), (LPVOID)hkCreateFileA, (LPVOID*)&oCreateFileA, "CreateFileA"}
};

// 循环创建钩子
int hookCount = sizeof(hooks) / sizeof(HOOK_ENTRY);
for (int i = 0; i < hookCount; i++)
{
    MH_CreateHook(hooks[i].target, hooks[i].detour, hooks[i].original);
    MH_EnableHook(hooks[i].target);
}

3.钩子其他进程的函数(进程注入)

MinHook 本身仅支持钩子当前进程的函数,如果需要钩子其他进程的函数,需配合Windows 进程注入技术

  • 将包含 MinHook 钩子逻辑的代码编译为DLL
  • 通过注入技术(如CreateRemoteThread+LoadLibraryA、远线程注入、反射注入)将 DLL 注入到目标进程;
  • 在 DLL 的DllMain函数中(DLL_PROCESS_ATTACH阶段)执行 MinHook 的初始化、创建、启用钩子逻辑;
  • 目标进程运行时,会执行 DLL 中的钩子逻辑,实现跨进程钩子。

8.MinHook 使用注意事项

  • 禁止直接调用目标函数:钩子启用后,直接调用目标函数会触发远跳,无限递归调用钩子函数,最终导致栈溢出崩溃;调用原函数必须通过跳板函数
  • 调用约定完全一致:x86 下的WINAPI(stdcall)cdeclthiscall必须显式指定,x64 下虽统一fastcall,但建议仍加WINAPI兼容;
  • 函数签名完全一致:返回值、参数列表(类型、个数、顺序)必须和目标函数完全一致,否则会导致参数传递错误、栈不平衡;
  • x64 寄存器保护:修改非易失寄存器必须push保存,pop恢复,示例:
int WINAPI hkFunc(...)
{
    push rbx; // 保存非易失寄存器
    push r12;
    // 自定义逻辑
    ...
    pop r12;  // 恢复非易失寄存器
    pop rbx;
    return oFunc(...);
}
  • 内存地址的正确性:确保目标函数的地址是真实的代码地址,避免获取到 DLL 的「转发器地址」(可通过GetProcAddress多次解析解决);
  • 避免钩子嵌套过深:钩子函数中尽量减少调用其他可能被钩子的函数,否则会导致逻辑混乱;
  • DllMain 中慎用DllMain是 Windows 的关键函数,在其中执行复杂的 MinHook 操作(如创建钩子)可能导致死锁,建议在 DLL 中创建线程,在线程中执行 MinHook 逻辑;
  • 权限问题:修改系统进程 / 保护进程的函数时,需要提升进程权限(SeDebugPrivilege),否则会导致内存权限修改失败;
  • 反调试 / 反钩子检测:部分程序 / 游戏会检测代码段的修改(MinHook 会修改目标函数的机器码),需做反检测处理(如指令加密、内存隐藏);
  • 资源释放:程序退出前必须调用MH_Uninitialize,即使中途出错,也需在异常处理中释放 MinHook 资源。

9.总结

MinHook 是 Windows 平台内联钩子的工业级解决方案,其核心价值在于封装了所有底层细节,让开发者无需掌握汇编、内存操作、指令分析等知识,就能快速实现稳定的函数钩子。

拓展学习

  • MinHook 官方源码:https://github.com/TsudaKageyu/minhook(可阅读底层实现,加深对 Inline Hook 的理解);
  • Windows 进程注入技术:CreateRemoteThread、反射注入、APC 注入(实现跨进程钩子的必备);
  • Windows 调用约定:stdcall/cdecl/thiscall/fastcall的差异(x86 和 x64 的区别);
  • 反调试 / 反钩子技术:指令加密、内存隐藏、CRC 校验(应对程序的反检测)。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐