跳至主要內容

60-API 挂钩 - Minhook 库

Maldevacademy大约 4 分钟安全开发

简介

Minhookopen in new window 是一个用 C 语言编写的钩子库,可用于实现 API 钩子。它兼容 Windows 上的 32 位和 64 位应用程序,并使用 x86/x64 汇编进行内联钩子,类似于 Detours 库。与其他钩子库相比,MinHook 更简单,并提供轻量级的 API,使其更容易使用。

使用 Minhook 库

与 Detours 库类似,Minhook 库需要在 Visual Studio 项目中包含静态 .lib 文件和 MinHook.hopen in new window 头文件。

Minhook API 函数

Minhook 函数库通过初始化一个包含挂钩安装或移除所需信息的结构来工作。这通过初始化库中 HOOK_ENTRYopen in new window 结构的 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.hopen in new window。返回的 MH_STATUS 数据类型表示指定函数的错误代码。如果函数成功,则返回一个为 0 的 MH_OK 值;如果出现错误,则返回一个非零值。

值得注意的是,MH_InitializeMH_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_CreateHookopen in new window 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
image

运行第二个已注入的 MessageBoxA

image
image

运行第三个未注入的 MessageBoxA

image
image