找回密码
 立即注册
首页 业界区 业界 MinHook 如何对.NET底层的 Win32函数 进行拦截(上) ...

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

少屠 2025-6-9 10:33:31
一:背景

1. 讲故事

在前面的系列中,我们聊过.NET外挂 harmony,他可以对.NET SDK方法进行拦截,这在.NET高级调试领域中非常重要,但这里也有一些遗憾,就是不能对SDK领域之外的函数进行拦截,比如 Win32 函数。。。
这篇我们就来解决这个问题,对,它就是 MinHook,当然我也调查了easyhook和detours,前者年久失修,后者是商业库,加上我的要求相对简单,使用极简版的 minhook 就够了,而且该项目在github上也是非常活跃的:https://github.com/TsudaKageyu/minhook
1.png


  • 开箱即用,下载 MinHook_134_bin.zip 即可。
  • 多项目融合,下载 MinHook_134_lib.zip 即可。
二:MinHook 案例演示

1. 开箱即用方式

观察 MinHook.h 头文件,会发现很多的 C 导出函数,如下所示:
  1. #ifdef __cplusplus
  2. extern "C" {
  3. #endif
  4.     // Initialize the MinHook library. You must call this function EXACTLY ONCE
  5.     // at the beginning of your program.
  6.     MH_STATUS WINAPI MH_Initialize(VOID);
  7.     // Uninitialize the MinHook library. You must call this function EXACTLY
  8.     // ONCE at the end of your program.
  9.     MH_STATUS WINAPI MH_Uninitialize(VOID);
  10.    
  11.     ...
  12. }
复制代码
有了这些导出函数,就可以通过 C# 的 PInvoke 直接调用,这里就演示一个拦截 MessageBox 方法,完整代码如下:
  1. using System;
  2. using System.Diagnostics;
  3. using System.Runtime.InteropServices;
  4. namespace ConsoleApp2
  5. {
  6.     internal class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             // 安装钩子
  11.             HookManager.InstallHook("user32.dll", "MessageBoxW",
  12.                 (IntPtr hWnd, string text, string caption, uint type) =>
  13.                 {
  14.                     Console.WriteLine($"我已成功拦截到 MessageBox:内容 {text}, 标题: {caption}");
  15.                     var original = Marshal.GetDelegateForFunctionPointer<HookManager.MessageBoxDelegate>(HookManager.OriginalFunction);
  16.                     return original(hWnd, text, caption, type);
  17.                 });
  18.             // 测试 MessageBox 调用(钩子会捕获这个)
  19.             MessageBox(IntPtr.Zero, "This is a test", "Test", 0);
  20.             // 卸载钩子
  21.             HookManager.UninstallHook();
  22.             Console.ReadLine();
  23.         }
  24.         [DllImport("user32.dll", CharSet = CharSet.Unicode)]
  25.         private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
  26.     }
  27.     public static class HookManager
  28.     {
  29.         // 定义 MessageBox 的委托
  30.         [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
  31.         public delegate int MessageBoxDelegate(IntPtr hWnd, string text, string caption, uint type);
  32.         // 原始函数的指针
  33.         public static IntPtr OriginalFunction = IntPtr.Zero;
  34.         // 当前钩子的目标函数地址
  35.         private static IntPtr _targetFunction = IntPtr.Zero;
  36.         public static void InstallHook(string moduleName, string functionName, MessageBoxDelegate detourFunc)
  37.         {
  38.             // 1. 初始化 MinHook
  39.             var status = MinHook.MH_Initialize();
  40.             if (status != MinHook.MH_STATUS.MH_OK)
  41.             {
  42.                 Console.WriteLine($"MH_Initialize failed: {status}");
  43.                 return;
  44.             }
  45.             // 2. 获取目标函数的地址
  46.             _targetFunction = MinHook.GetProcAddress(MinHook.GetModuleHandle(moduleName), functionName);
  47.             if (_targetFunction == IntPtr.Zero)
  48.             {
  49.                 Console.WriteLine($"Failed to get {functionName} address");
  50.                 return;
  51.             }
  52.             // 3. 创建钩子
  53.             var detourPtr = Marshal.GetFunctionPointerForDelegate(detourFunc);
  54.             status = MinHook.MH_CreateHook(_targetFunction, detourPtr, out OriginalFunction);
  55.             if (status != MinHook.MH_STATUS.MH_OK)
  56.             {
  57.                 Console.WriteLine($"MH_CreateHook failed: {status}");
  58.                 return;
  59.             }
  60.             // 4. 启用钩子
  61.             status = MinHook.MH_EnableHook(_targetFunction);
  62.             if (status != MinHook.MH_STATUS.MH_OK)
  63.             {
  64.                 Console.WriteLine($"MH_EnableHook failed: {status}");
  65.                 return;
  66.             }
  67.             Console.WriteLine($"{functionName} hook installed successfully");
  68.         }
  69.         public static void UninstallHook()
  70.         {
  71.             if (_targetFunction == IntPtr.Zero)
  72.             {
  73.                 Console.WriteLine("No active hook to uninstall");
  74.                 return;
  75.             }
  76.             // 1. 禁用钩子
  77.             var status = MinHook.MH_DisableHook(_targetFunction);
  78.             if (status != MinHook.MH_STATUS.MH_OK)
  79.             {
  80.                 Console.WriteLine($"MH_DisableHook failed: {status}");
  81.             }
  82.             // 2. 移除钩子
  83.             status = MinHook.MH_RemoveHook(_targetFunction);
  84.             if (status != MinHook.MH_STATUS.MH_OK)
  85.             {
  86.                 Console.WriteLine($"MH_RemoveHook failed: {status}");
  87.             }
  88.             // 3. 卸载 MinHook
  89.             status = MinHook.MH_Uninitialize();
  90.             if (status != MinHook.MH_STATUS.MH_OK)
  91.             {
  92.                 Console.WriteLine($"MH_Uninitialize failed: {status}");
  93.             }
  94.             _targetFunction = IntPtr.Zero;
  95.             OriginalFunction = IntPtr.Zero;
  96.             Console.WriteLine("Hook uninstalled successfully");
  97.         }
  98.     }
  99.     public class MinHook
  100.     {
  101.         // MH_STATUS 枚举
  102.         public enum MH_STATUS
  103.         {
  104.             MH_UNKNOWN = -1,
  105.             MH_OK = 0,
  106.             MH_ERROR_ALREADY_INITIALIZED,
  107.             MH_ERROR_NOT_INITIALIZED,
  108.             MH_ERROR_ALREADY_CREATED,
  109.             MH_ERROR_NOT_CREATED,
  110.             MH_ERROR_ENABLED,
  111.             MH_ERROR_DISABLED,
  112.             MH_ERROR_NOT_EXECUTABLE,
  113.             MH_ERROR_UNSUPPORTED_FUNCTION,
  114.             MH_ERROR_MEMORY_ALLOC,
  115.             MH_ERROR_MEMORY_PROTECT,
  116.             MH_ERROR_MODULE_NOT_FOUND,
  117.             MH_ERROR_FUNCTION_NOT_FOUND
  118.         }
  119.         public IntPtr MH_ALL_HOOKS = IntPtr.Zero;
  120.         // 导入 MinHook 函数
  121.         [DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
  122.         public static extern MH_STATUS MH_Initialize();
  123.         [DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
  124.         public static extern MH_STATUS MH_Uninitialize();
  125.         [DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
  126.         public static extern MH_STATUS MH_CreateHook(IntPtr pTarget, IntPtr pDetour, out IntPtr ppOriginal);
  127.         [DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
  128.         public static extern MH_STATUS MH_CreateHookApi(string pszModule, string pszProcName, IntPtr pDetour, out IntPtr ppOriginal);
  129.         [DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
  130.         public static extern MH_STATUS MH_RemoveHook(IntPtr pTarget);
  131.         [DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
  132.         public static extern MH_STATUS MH_EnableHook(IntPtr pTarget);
  133.         [DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
  134.         public static extern MH_STATUS MH_DisableHook(IntPtr pTarget);
  135.         [DllImport("MinHook.x86.dll", CallingConvention = CallingConvention.Cdecl)]
  136.         public static extern IntPtr MH_StatusToString(MH_STATUS status);
  137.         [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  138.         public static extern IntPtr GetModuleHandle(string lpModuleName);
  139.         [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
  140.         public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
  141.     }
  142. }
复制代码
由于这个 C# 程序是 32bit 的,所以将 MinHook.x86.dll 拷贝到C#程序的当前目录。
2.png

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

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

  • 当你用 vs 单步调试的时候,走到 MH_CreateHook 方法时会抛 Fatal error. Internal CLR error. (0x80131506)  CLR 内部错误,看起来VS调试器做了一些手脚,截图如下:
4.png


  • 当你想在 InstallHook 中的 detourFunc 函数下断点,也不会被命中,截图如下:
5.png

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

用 C# 的 Pinvoke 调用 MinHook,总感觉少了那个味,如果用原汁原味的 C 调用 MinHook 那就相当完美了,在解决一些比较复杂注入非常有必要。
这里我采用 libMinHook.x86.lib 以静态链接的方式将当前的 ConsoleApplication2 和 MinHook 合二为一,截图如下:
6.png

接下来做三点配置:

  • 右键属性 配置下 头文件 和 dll库 搜索路径,截图如下:
7.png


  • 右键属性 配置下 依赖文件名,截图如下:
8.png

最后就是完整的 C 代码。
  1. #include <windows.h>
  2. #include <stdio.h>
  3. #include <fcntl.h>
  4. #include <io.h>
  5. #include <MinHook.h>
  6. typedef int (WINAPI* Real_MessageBoxW)(
  7.         HWND hWnd,
  8.         LPCWSTR lpText,
  9.         LPCWSTR lpCaption,
  10.         UINT uType
  11.         );
  12. Real_MessageBoxW fpMessageBoxW = NULL;
  13. int WINAPI Hook_MessageBoxW(
  14.         HWND hWnd,
  15.         LPCWSTR lpText,
  16.         LPCWSTR lpCaption,
  17.         UINT uType)
  18. {
  19.         _setmode(_fileno(stdout), _O_U16TEXT);
  20.         wprintf(L"拦截 MessageBoxW:\n");
  21.         wprintf(L"  文本: %s\n", lpText);  // 移除 &,直接使用 lpText
  22.         wprintf(L"  标题: %s\n", lpCaption);  // 移除 &,直接使用 lpCaption
  23.         return fpMessageBoxW(
  24.                 hWnd,
  25.                 lpText,
  26.                 lpCaption,
  27.                 uType);
  28. }
  29. extern "C" __declspec(dllexport) void InstallHook()
  30. {
  31.         HMODULE hModule = GetModuleHandleW(L"user32.dll");
  32.         Real_MessageBoxW pOrigMessageBoxW = (Real_MessageBoxW)GetProcAddress(hModule, "MessageBoxW");
  33.         if (pOrigMessageBoxW == NULL) {
  34.                 printf("无法获取 MessageBoxW 地址\n");
  35.                 return;
  36.         }
  37.         if (MH_Initialize() != MH_OK) {
  38.                 printf("MinHook 初始化失败\n");
  39.                 return;
  40.         }
  41.         if (MH_CreateHook(pOrigMessageBoxW, &Hook_MessageBoxW, (void**)&fpMessageBoxW) != MH_OK) {
  42.                 printf("创建 Hook 失败\n");
  43.                 return;
  44.         }
  45.         if (MH_EnableHook(pOrigMessageBoxW) != MH_OK) {
  46.                 printf("启用 Hook 失败\n");
  47.                 return;
  48.         }
  49.         printf("MessageBoxW Hook 安装成功\n");
  50. }
  51. extern "C" __declspec(dllexport) void UninstallHook()
  52. {
  53.         MH_DisableHook(MH_ALL_HOOKS);
  54.         MH_Uninitialize();
  55. }
复制代码
这是只对外公开了 InstallHook 和 UninstallHook 装卸载方法,最后就是高层的 C# 代码。
  1. using System;
  2. using System.Runtime.InteropServices;
  3. namespace ConsoleApp1
  4. {
  5.     internal class Program
  6.     {
  7.         static void Main(string[] args)
  8.         {
  9.             Console.WriteLine("正在安装 MessageBox 钩子...");
  10.             InstallHook();
  11.             Console.WriteLine("将弹出测试消息框,观察输出...");
  12.             // 弹出测试消息框(会被钩子拦截)
  13.             MessageBoxW(IntPtr.Zero, "这是测试内容", "测试标题", 0);
  14.             Console.WriteLine("按任意键退出并卸载钩子...");
  15.             Console.ReadKey();
  16.             UninstallHook();
  17.             Console.WriteLine("钩子已卸载");
  18.         }
  19.         // 导入 user32.dll 的 MessageBoxW
  20.         [DllImport("user32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
  21.         public static extern int MessageBoxW(
  22.             IntPtr hWnd,
  23.             string lpText,
  24.             string lpCaption,
  25.             uint uType);
  26.         // 导入DLL中的函数
  27.         [DllImport("ConsoleApplication2.dll", CallingConvention = CallingConvention.Cdecl)]
  28.         public static extern void InstallHook();
  29.         [DllImport("ConsoleApplication2.dll", CallingConvention = CallingConvention.Cdecl)]
  30.         public static extern void UninstallHook();
  31.     }
  32. }
复制代码
在运行之前将 ConsoleApplication2 拷贝到 C# 程序的当前目录,直接运行 C# 项目即可。
9.png

三:总结

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


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册