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.运行
这是靶子程序运行的效果.
DLL注入成功打印的字符串
“DLL Injected:Execute”
Hook 创建成功的字符串
“MH_CreateHook() called from Execute:OK”
根据DLL代码,我们按下insert键,将会启用关于Test函数的Hook
按下Delete键,我们将会关闭关于Test函数的Hook
如果我们卸载掉DLL,将会执行MinHook的MH_Uninitialize()函数
它会自动为我们还原之前的状态