目录1.简介2.安装与集成3.MinHook 核心 API 详解4.MinHook 底层核心原理5.MinHook 基本使用流程6.完整可运行示例钩子 MessageBoxA7.MinHook 进阶用法与场景8.MinHook 使用注意事项9.总结拓展学习1.简介MinHook 是由 Tsuda Kageyu 开发的开源库MIT 协议仅支持x8632 位和 x6464 位Windows核心目标是让开发者以极简的代码实现稳定的内联钩子无需手动处理内存权限、指令跳转、地址计算等底层细节。核心优势轻量级源码仅千行级预编译库体积极小静态库 100KB无任何第三方依赖跨架构完美支持 x86/x64自动处理两种架构的指令差异和地址计算易用性提供简洁的 C 语言 API固定的使用流程新手半小时即可上手稳定性自动处理内存权限修改、指令对齐、ASLR地址空间布局随机化等问题底层做了大量兼容性处理线程安全核心 API 的原子操作设计多线程环境下使用无额外风险低侵入性钩子移除后可完全恢复目标函数的原始代码无残留。使用场景拦截 Windows 系统 API如MessageBoxA、CreateFileA、WSASend等调试 / 逆向中监控目标进程的函数调用参数、返回值、调用栈软件功能扩展在原函数执行前 / 后插入自定义逻辑游戏修改钩子游戏内的渲染、输入、网络函数钩子自定义动态库DLL或进程内的本地函数。2.安装与集成下载地址https://gitcode.com/gh_mirrors/mi/minhook1.源码编译适合定制化 / 最新功能克隆源码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 minhook3.MinHook 核心 API 详解MinHook 的所有 API 都定义在MinHook.h中返回值均为MH_STATUS枚举表示操作结果核心 API 仅 10 个左右分为初始化 / 卸载、钩子操作、辅助调试三类以下是最常用的核心 API 详解。1.基础类型MH_STATUS操作结果枚举所有 API 的返回值核心取值完整取值见MinHook.htypedef 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_InitializeMH_STATUS MH_CDECL MH_Initialize(void);功能初始化 MinHook 的全局资源如内存池、线程同步对象注意进程内仅需调用一次多次调用会返回MH_ERROR_ALREADY_INITIALIZED。2)MH_UninitializeMH_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.辅助调试 API1)MH_StatusToStringconst char* MH_CDECL MH_StatusToString(MH_STATUS status);功能将MH_STATUS错误码转为人类可读的字符串如MH_ERROR_ALREADY_INITIALIZED→Already initialized适用场景调试时快速定位 API 调用失败的原因。2)MH_GetHookInfoMH_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/x641)x8632 位架构目标函数的前5 个字节是 x86 远跳指令JMP DWORD PTR [addr]的固定长度MinHook 先保存这 5 个原始字节修改目标函数的内存权限为PAGE_EXECUTE_READWRITE默认代码段是只读可执行无法修改将目标函数前 5 字节替换为远跳指令跳转到开发者定义的钩子函数创建跳板函数Trampoline将保存的 5 个原始字节复制到跳板函数再在跳板函数末尾添加跳回「目标函数第 6 字节」的指令实现原函数的完整执行。2)x6464 位架构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/thiscallx64 下 Windows 统一fastcallMinHook 自动适配但开发者需显式指定跳板函数为函数指针由 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 系统 APIuser32.dll中的MessageBoxA弹窗函数为例实现修改弹窗标题和内容的功能该示例可直接在 VS 中编译运行x86/x64 均可只需对应配置库文件。#include Windows.h #include MinHook.h #include cstdio // 链接MinHook静态库x86/x64对应各自的lib #pragma comment(lib, minhook.lib) // 1. 定义MessageBoxA的函数指针类型WINAPIstdcallx86下必须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); // 后置逻辑打印弹窗的返回值IDOK1IDCANCEL2 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通过注入技术如CreateRemoteThreadLoadLibraryA、远线程注入、反射注入将 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 校验应对程序的反检测。