百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

MinHook 如何对.NET底层的 Win32函数 进行拦截(上)

bigegpt 2025-06-13 11:27 7 浏览

一:背景

1. 讲故事

在前面的系列中,我们聊过.NET外挂 harmony,他可以对.NET SDK方法进行拦截,这在.NET高级调试领域中非常重要,但这里也有一些遗憾,就是不能对SDK领域之外的函数进行拦截,比如 Win32 函数。。。

这篇我们就来解决这个问题,对,它就是 MinHook,当然我也调查了easyhookdetours,前者年久失修,后者是商业库,加上我的要求相对简单,使用极简版的 minhook 就够了,而且该项目在github上也是非常活跃的:https://github.com/TsudaKageyu/minhook

  • 开箱即用,下载 MinHook_134_bin.zip 即可。
  • 多项目融合,下载 MinHook_134_lib.zip 即可。

二:MinHook 案例演示

1. 开箱即用方式

观察 MinHook.h 头文件,会发现很多的 C 导出函数,如下所示:


#ifdef __cplusplus
extern"C" {
#endif

// Initialize the MinHook library. You must call this function EXACTLY ONCE
// at the beginning of your program.
MH_STATUS WINAPI MH_Initialize(VOID);

// Uninitialize the MinHook library. You must call this function EXACTLY
// ONCE at the end of your program.
MH_STATUS WINAPI MH_Uninitialize(VOID);

...
}

有了这些导出函数,就可以通过 C# 的 PInvoke 直接调用,这里就演示一个拦截 MessageBox 方法,完整代码如下:


using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespaceConsoleApp2
{
internalclassProgram
{
static void Main(string[] args)
{
// 安装钩子
HookManager.InstallHook("user32.dll", "MessageBoxW",
(IntPtr hWnd, string text, string caption, uint type) =>
{
Console.WriteLine($"我已成功拦截到 MessageBox:内容 {text}, 标题: {caption}");

var original = Marshal.GetDelegateForFunctionPointer<HookManager.MessageBoxDelegate>(HookManager.OriginalFunction);

return original(hWnd, text, caption, type);
});

// 测试 MessageBox 调用(钩子会捕获这个)
MessageBox(IntPtr.Zero, "This is a test", "Test", 0);

// 卸载钩子
HookManager.UninstallHook();

Console.ReadLine();
}

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}

publicstaticclassHookManager
{
// 定义 MessageBox 的委托
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public delegate int MessageBoxDelegate(IntPtr hWnd, string text, string caption, uint type);

// 原始函数的指针
publicstatic IntPtr OriginalFunction = IntPtr.Zero;

// 当前钩子的目标函数地址
privatestatic IntPtr _targetFunction = IntPtr.Zero;

public static void InstallHook(string moduleName, string functionName, MessageBoxDelegate detourFunc)
{
// 1. 初始化 MinHook
var status = MinHook.MH_Initialize();
if (status != MinHook.MH_STATUS.MH_OK)
{
Console.WriteLine($"MH_Initialize failed: {status}");
return;
}

// 2. 获取目标函数的地址
_targetFunction = MinHook.GetProcAddress(MinHook.GetModuleHandle(moduleName), functionName);
if (_targetFunction == IntPtr.Zero)
{
Console.WriteLine($"Failed to get {functionName} address");
return;
}

// 3. 创建钩子
var detourPtr = Marshal.GetFunctionPointerForDelegate(detourFunc);
status = MinHook.MH_CreateHook(_targetFunction, detourPtr, out OriginalFunction);

if (status != MinHook.MH_STATUS.MH_OK)
{
Console.WriteLine($"MH_CreateHook failed: {status}");
return;
}

// 4. 启用钩子
status = MinHook.MH_EnableHook(_targetFunction);
if (status != MinHook.MH_STATUS.MH_OK)
{
Console.WriteLine($"MH_EnableHook failed: {status}");
return;
}

Console.WriteLine($"{functionName} hook installed successfully");
}

public static void UninstallHook()
{
if (_targetFunction == IntPtr.Zero)
{
Console.WriteLine("No active hook to uninstall");
return;
}

// 1. 禁用钩子
var status = MinHook.MH_DisableHook(_targetFunction);
if (status != MinHook.MH_STATUS.MH_OK)
{
Console.WriteLine($"MH_DisableHook failed: {status}");
}

// 2. 移除钩子
status = MinHook.MH_RemoveHook(_targetFunction);
if (status != MinHook.MH_STATUS.MH_OK)
{
Console.WriteLine($"MH_RemoveHook failed: {status}");
}

// 3. 卸载 MinHook
status = MinHook.MH_Uninitialize();
if (status != MinHook.MH_STATUS.MH_OK)
{
Console.WriteLine($"MH_Uninitialize failed: {status}");
}

_targetFunction = IntPtr.Zero;
OriginalFunction = IntPtr.Zero;

Console.WriteLine("Hook uninstalled successfully");
}
}

publicclassMinHook
{
// MH_STATUS 枚举
publicenum MH_STATUS
{
MH_UNKNOWN = -1,
MH_OK = 0,
MH_ERROR_ALREADY_INITIALIZED,
MH_ERROR_NOT_INITIALIZED,
MH_ERROR_ALREADY_CREATED,
MH_ERROR_NOT_CREATED,
MH_ERROR_ENABLED,
MH_ERROR_DISABLED,
MH_ERROR_NOT_EXECUTABLE,
MH_ERROR_UNSUPPORTED_FUNCTION,
MH_ERROR_MEMORY_ALLOC,
MH_ERROR_MEMORY_PROTECT,
MH_ERROR_MODULE_NOT_FOUND,
MH_ERROR_FUNCTION_NOT_FOUND
}

public IntPtr MH_ALL_HOOKS = IntPtr.Zero;

// 导入 MinHook 函数
[DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern MH_STATUS MH_Initialize();

[DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern MH_STATUS MH_Uninitialize();

[DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern MH_STATUS MH_CreateHook(IntPtr pTarget, IntPtr pDetour, out IntPtr ppOriginal);

[DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern MH_STATUS MH_CreateHookApi(string pszModule, string pszProcName, IntPtr pDetour, out IntPtr ppOriginal);

[DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern MH_STATUS MH_RemoveHook(IntPtr pTarget);

[DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern MH_STATUS MH_EnableHook(IntPtr pTarget);

[DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern MH_STATUS MH_DisableHook(IntPtr pTarget);

[DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr MH_StatusToString(MH_STATUS status);


[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
}
}

由于这个 C# 程序是 32bit 的,所以将 MinHook.x86.dll 拷贝到C#程序的当前目录。

最后将程序运行起来,该有的都出现了。

个人实践下来,我发现有两个小问题:

  1. 当你用 vs 单步调试的时候,走到 MH_CreateHook 方法时会抛 Fatal error. Internal CLR error. (0x80131506) CLR 内部错误,看起来VS调试器做了一些手脚,截图如下:
2. 当你想在 InstallHook 中的 detourFunc 函数下断点,也不会被命中,截图如下:

总的来说对VS调试有一点影响,但也不太大,还是可以接受的,毕竟用 C# 来调用轻车熟路,如果你熟悉 windbg 的话,用它是一点问题都没有。。。

2. 深度融合方式

用 C# 的 Pinvoke 调用 MinHook,总感觉少了那个味,如果用原汁原味的 C 调用 MinHook 那就相当完美了,在解决一些比较复杂注入非常有必要。

这里我采用 libMinHook.x86.lib 以静态链接的方式将当前的 ConsoleApplication2 和 MinHook 合二为一,截图如下:

接下来做三点配置:

  1. 右键属性 配置下 头文件 和 dll库 搜索路径,截图如下:
2. 右键属性 配置下 依赖文件名,截图如下:

最后就是完整的 C 代码。


#include <windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <MinHook.h>

typedef int (WINAPI* Real_MessageBoxW)(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
)
;

Real_MessageBoxW fpMessageBoxW = ;

int WINAPI Hook_MessageBoxW(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType)

{
_setmode(_fileno(stdout), _O_U16TEXT);

wprintf(L"拦截 MessageBoxW:\n");
wprintf(L" 文本: %s\n", lpText); // 移除 &,直接使用 lpText
wprintf(L" 标题: %s\n", lpCaption); // 移除 &,直接使用 lpCaption

return fpMessageBoxW(
hWnd,
lpText,
lpCaption,
uType);
}

extern"C" __declspec(dllexport) void InstallHook()
{
HMODULE hModule = GetModuleHandleW(L"user32.dll");
Real_MessageBoxW pOrigMessageBoxW = (Real_MessageBoxW)GetProcAddress(hModule, "MessageBoxW");

if (pOrigMessageBoxW == ) {
printf("无法获取 MessageBoxW 地址\n");
return;
}

if (MH_Initialize() != MH_OK) {
printf("MinHook 初始化失败\n");
return;
}

if (MH_CreateHook(pOrigMessageBoxW, &Hook_MessageBoxW, (void**)&fpMessageBoxW) != MH_OK) {
printf("创建 Hook 失败\n");
return;
}

if (MH_EnableHook(pOrigMessageBoxW) != MH_OK) {
printf("启用 Hook 失败\n");
return;
}

printf("MessageBoxW Hook 安装成功\n");
}

extern"C" __declspec(dllexport) void UninstallHook()
{
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
}

这是只对外公开了 InstallHook 和 UninstallHook 装卸载方法,最后就是高层的 C# 代码。


using System;
using System.Runtime.InteropServices;

namespaceConsoleApp1
{
internalclassProgram
{
static void Main(string[] args)
{
Console.WriteLine("正在安装 MessageBox 钩子...");
InstallHook();

Console.WriteLine("将弹出测试消息框,观察输出...");

// 弹出测试消息框(会被钩子拦截)
MessageBoxW(IntPtr.Zero, "这是测试内容", "测试标题", 0);

Console.WriteLine("按任意键退出并卸载钩子...");
Console.ReadKey();

UninstallHook();
Console.WriteLine("钩子已卸载");
}

// 导入 user32.dll 的 MessageBoxW
[DllImport("user32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
public static extern int MessageBoxW(
IntPtr hWnd,
string lpText,
string lpCaption,
uint uType
)
;

// 导入DLL中的函数
[DllImport("ConsoleApplication2.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void InstallHook();

[DllImport("ConsoleApplication2.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void UninstallHook();
}
}

在运行之前将 ConsoleApplication2 拷贝到 C# 程序的当前目录,直接运行 C# 项目即可。

三:总结

开箱和融合两种方式都有自己的用途,下一篇我们上一个很真实的场景,让大家体会下 win32 的注入对高级调试领域 的强大功效。


相关推荐

了解Linux目录,那你就了解了一半的Linux系统

大到公司或者社群再小到个人要利用Linux来开发产品的人实在是多如牛毛,每个人都用自己的标准来配置文件或者设置目录,那么未来的Linux则就是一团乱麻,也对管理造成许多麻烦。后来,就有所谓的FHS(F...

Linux命令,这些操作要注意!(linux命令?)

刚玩Linux的人总觉得自己在演黑客电影,直到手滑输错命令把公司服务器删库,这才发现命令行根本不是随便乱用的,而是“生死簿”。今天直接上干货,告诉你哪些命令用好了封神!喜欢的一键三连,谢谢观众老爷!!...

Linux 命令速查手册:这 30 个高频指令,拯救 90% 的运维小白!

在Linux系统的世界里,命令行是强大的武器。对于运维小白而言,掌握一些高频使用的Linux命令,能极大提升工作效率,轻松应对各种系统管理任务。今天,就为大家奉上精心整理的30个Linu...

linux必学的60个命令(linux必学的20个命令)

以下是Linux必学的20个基础命令:1.cd:切换目录2.ls:列出文件和目录3.mkdir:创建目录4.rm:删除文件或目录5.cp:复制文件或目录6.mv:移动/重命名文件或目录7....

提高工作效率的--Linux常用命令,能够决解95%以上的问题

点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...

15 个实用 Linux 命令(linux命令用法及举例)

Linux命令行是系统管理员、开发者和技术爱好者的强大工具。掌握实用命令不仅能提高效率,还能解锁Linux系统的无限潜力,本文将深入介绍15个实用Linux命令。ls-列出目录内容l...

Linux 常用命令集合(linux常用命令全集)

系统信息arch显示机器的处理器架构(1)uname-m显示机器的处理器架构(2)uname-r显示正在使用的内核版本dmidecode-q显示硬件系统部件-(SMBIOS/DM...

Linux的常用命令就是记不住,怎么办?

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux常用文件操作命令(linux常用文件操作命令有哪些)

ls命令在Linux维护工作中,经常使用ls这个命令,这是最基本的命令,来写几条常用的ls命令。先来查看一下使用的ls版本#ls--versionls(GNUcoreutils)8.4...

Linux 常用命令(linux常用命令)

日志排查类操作命令查看日志cat/var/log/messages、tail-fxxx.log搜索关键词grep"error"xxx.log多条件过滤`grep-E&#...

简单粗暴收藏版:Linux常用命令大汇总

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友在Linux系统中,命令行界面(CLI)是管理员和开发人员最常用的工具之一。通过命令行,用户可...

「Linux」linux常用基本命令(linux常用基本命令和用法)

Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们。总结送免费学习资料(包含视频、技术学习路线图谱、文档等)1、显示日期的指令:d...

Linux的常用命令就是记不住,怎么办?于是推出了这套教程

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux的30个常用命令汇总,运维大神必掌握技能!

以下是Linux系统中最常用的30个命令,精简版覆盖日常操作核心需求,适合快速掌握:一、文件/目录操作1.`ls`-列出目录内容`ls-l`(详细信息)|`ls-a`(显示隐藏文件)...

Linux/Unix 系统中非常常用的命令

Linux/Unix系统中非常常用的命令,它们是进行文件操作、文本处理、权限管理等任务的基础。下面是对这些命令的简要说明:**文件操作类:*****`ls`(list):**列出目录内容,显...