60-API 挂钩 - Minhook 库
简介
Minhook 是一个用 C 语言编写的钩子库,可用于实现 API 钩子。它兼容 Windows 上的 32 位和 64 位应用程序,并使用 x86/x64 汇编进行内联钩子,类似于 Detours 库。与其他钩子库相比,MinHook 更简单,并提供轻量级的 API,使其更容易使用。
使用 Minhook 库
与 Detours 库类似,Minhook 库需要在 Visual Studio 项目中包含静态 .lib
文件和 MinHook.h 头文件。
Minhook API 函数
Minhook 函数库通过初始化一个包含挂钩安装或移除所需信息的结构来工作。这通过初始化库中 HOOK_ENTRY 结构的 MH_Initialize API 来完成。接下来,MH_CreateHook 函数用来创建挂钩,而 MH_EnableHook 用于启用它们。MH_DisableHook 用于移除挂钩,最后,MH_Uninitialize 用于清理已经初始化的结构。为了方便起见,这些函数在下面再次列出。
MH_Initialize - 初始化 HOOK_ENTRY 结构。
MH_CreateHook - 创建挂钩。
MH_EnableHook - 启用已创建的挂钩。
MH_DisableHook - 移除挂钩。
MH_Uninitialize - 清理已初始化的结构。
Minhook API 返回一个 MH_STATUS 值,其是一个用户自定义的枚举,位于 MinHook.h。返回的 MH_STATUS 数据类型表示指定函数的错误代码。如果函数成功,则返回一个为 0 的 MH_OK 值;如果出现错误,则返回一个非零值。
值得注意的是,MH_Initialize 和 MH_Uninitialize 函数都应该只调用一次,分别在程序的开始和结束处调用。
Detour 函数
此模块将利用前一模块中相同的 MessageBoxA API 示例,该示例将被钩取并更改为执行不同的消息框。
fnMessageBoxA g_pMessageBoxA = NULL;
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
printf("[+] 原始参数:\n");
printf("\t - lpText\t: %s\n", lpText);
printf("\t - lpCaption\t: %s\n", lpCaption);
return g_pMessageBoxA(hWnd, "不同 lpText", "不同 lpCaption", uType);
}
请注意,g_pMessageBoxA
全局变量用于运行消息框,其中 g_pMessageBoxA
是指向原始未钩取的 MessageBoxA API 的指针。将其设置为 NULL
,因为 Minhook MH_CreateHook API 调用是将其初始化为供使用的调用,而不是像 Detours 库那样手动设置 g_pMessageBoxA
。这样做是为了防止出现钩取循环问题,这个问题在前一模块中已讨论过。
Minhook Hooking 流程
如前所述,要使用 Minhook 劫持特定的 API,首先需要执行 MH_Initialize
函数。然后可以用 MH_CreateHook
创建 Hook,并用 MH_EnableHook
启用 Hook。
BOOL InstallHook() {
DWORD dwMinHookErr = NULL; // Minhook 错误码
if ((dwMinHookErr = MH_Initialize()) != MH_OK) {
printf("[!] MH_Initialize 报错:%d \n", dwMinHookErr);
return FALSE;
}
// 在 MessageBoxA 上安装 Hook,改用 MyMessageBoxA 运行
// g_pMessageBoxA 将指向原始的 MessageBoxA 函数
if ((dwMinHookErr = MH_CreateHook(&MessageBoxA, &MyMessageBoxA, &g_pMessageBoxA)) != MH_OK) {
printf("[!] MH_CreateHook 报错:%d \n", dwMinHookErr);
return FALSE;
}
// 启用 MessageBoxA 上的 Hook
if ((dwMinHookErr = MH_EnableHook(&MessageBoxA)) != MH_OK) {
printf("[!] MH_EnableHook 报错:%d \n", dwMinHookErr);
return -1;
}
return TRUE;
}
Minhook 解除挂钩的例程
与 Detours 库不同,Minhook 库不需要使用事务。相反,要解除一个钩子,只需要使用 MH_DisableHook
API,并传入已挂钩函数的地址。MH_Uninitialize
调用是可选的,但它会清理由前一个 MH_Initialize
调用初始化的结构。
BOOL Unhook() {
DWORD dwMinHookErr = NULL; // Minhook 错误代码
if ((dwMinHookErr = MH_DisableHook(&MessageBoxA)) != MH_OK) { // 解除 MessageBoxA 函数的挂钩
printf("[!] MH_DisableHook 失败,错误代码:%d\n", dwMinHookErr);
return FALSE;
}
if ((dwMinHookErr = MH_Uninitialize()) != MH_OK) { // 清理 Minhook 库内部数据
printf("[!] MH_Uninitialize 失败,错误代码:%d\n", dwMinHookErr);
return FALSE;
}
return TRUE;
}
主函数
前面展示的 Hook 和 Unhook 例程不包含主函数。主函数如下所示,它仅仅调用 MessageBoxA
的原始版本和 Hook 版本。
int main() {
// 将运行
MessageBoxA(NULL, "您对恶意软件开发有什么看法?", "原始消息框", MB_OK | MB_ICONQUESTION);
// Hook
if (!InstallHook())
return -1;
// 将不会运行 - 已 Hook
MessageBoxA(NULL, "恶意软件开发很糟糕", "原始消息框", MB_OK | MB_ICONWARNING);
// Unhook
if (!Unhook())
return -1;
// 将运行 - 禁用 Hook
MessageBoxA(NULL, "再次正常消息框", "原始消息框", MB_OK | MB_ICONINFORMATION);
return 0;
}
示例演示
运行第一个未注入的 MessageBoxA
![image](https://maldevacademy.s3.amazonaws.com/images/Intermediate/minhook-113692839-29b30634-f82b-49a1-9bbc-9a27277431b2.png)
运行第二个已注入的 MessageBoxA
![image](https://maldevacademy.s3.amazonaws.com/images/Intermediate/minhook-213692909-51d8413a-eb9a-44a3-b59c-a43fc6fa5113.png)
运行第三个未注入的 MessageBoxA
![image](https://maldevacademy.s3.amazonaws.com/images/Intermediate/minhook-313692968-0b322f31-7913-48b2-95bf-15e5088aa0af.png)