MinHook:Windows 平台下轻量级、高性能的钩子库
MinHook是一款轻量级Windows内联钩子库,支持x86/x64架构,通过修改目标函数机器码实现函数重定向。核心优势包括:自动处理内存权限、指令跳转和地址计算;提供简洁API(初始化、创建/启用/禁用钩子、卸载);线程安全且低侵入性。使用流程固定:获取目标函数地址→创建钩子→启用钩子→通过跳板函数调用原函数。典型应用场景包括API拦截、调试监控和游戏修改。注意事项包括:必须保持函数签名一致、
目录
1.简介
MinHook 是由 Tsuda Kageyu 开发的开源库(MIT 协议),仅支持x86(32 位)和 x64(64 位)Windows,核心目标是让开发者以极简的代码实现稳定的内联钩子,无需手动处理内存权限、指令跳转、地址计算等底层细节。
核心优势:
- 轻量级:源码仅千行级,预编译库体积极小(静态库 < 100KB),无任何第三方依赖;
- 跨架构:完美支持 x86/x64,自动处理两种架构的指令差异和地址计算;
- 易用性:提供简洁的 C 语言 API,固定的使用流程,新手半小时即可上手;
- 稳定性:自动处理内存权限修改、指令对齐、ASLR(地址空间布局随机化)等问题,底层做了大量兼容性处理;
- 线程安全:核心 API 的原子操作设计,多线程环境下使用无额外风险;
- 低侵入性:钩子移除后可完全恢复目标函数的原始代码,无残留。
使用场景:
- 拦截 Windows 系统 API(如
MessageBoxA、CreateFileA、WSASend等); - 调试 / 逆向中监控目标进程的函数调用(参数、返回值、调用栈);
- 软件功能扩展(在原函数执行前 / 后插入自定义逻辑);
- 游戏修改(钩子游戏内的渲染、输入、网络函数);
- 钩子自定义动态库(DLL)或进程内的本地函数。
2.安装与集成
1.源码编译(适合定制化 / 最新功能)
- 克隆源码:
git clonehttps://gitcode.com/gh_mirrors/mi/minhook.git - 生成工程:源码根目录有
CMakeLists.txt,用 CMake 生成 Visual Studio 工程(选择对应架构); - 编译工程:用 VS 打开生成的工程,编译
minhook项目,会在build/lib下生成 x86/x64 的库文件; - 后续配置:和方式 1 一致,将编译后的
include和lib配置到自己的项目中。
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 个步骤,核心是保证函数签名、调用约定一致。
核心前置要求
钩子函数、跳板函数、目标函数必须满足:
- 函数签名完全一致(返回值类型、参数列表完全相同);
- 调用约定完全一致(关键!x86 下常见
stdcall/cdecl/thiscall,x64 下 Windows 统一fastcall,MinHook 自动适配,但开发者需显式指定); - 跳板函数为函数指针,由 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 下默认
thiscall(this指针通过 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)、cdecl、thiscall必须显式指定,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 校验(应对程序的反检测)。
更多推荐

所有评论(0)