MinHook 如何对 .NET 母体 CoreCLR 进行拦截
一:背景
1. 讲故事
这篇文章起源于和一家 .NET公司 开线上会议时,提出的一个场景问题,程序出现了非托管内存暴涨,这些非托管内存关联的对象都囤积在 终结器队列 中,很显然这是代码中没用 using 及时释放引发的,而这块代码又是第三方组件,你想加也加不了,所以提出了一个设想:能不能设法干预 终结器队列的 freachable 节段,让里面的对象提前释放,而不是等待不稳定的终结器线程来兜底。。。
这个问题我最近也在考虑,毕竟我写过如何用 harmony 拦截 .net sdk ,用 minhook 拦截 win32api,唯独这一块没有跟大家聊,虽然 eventpipe 给 coreclr 开了很多的日志口子,但怎么说呢? eventpipe 是一种君子之法,和黑客性质的minhook无法相提并论,所以这一篇就详细的和大家聊一聊。
二:如何拦截 coreclr
1. 一个小案例
为了方便演示,就以拦截 GC.Collect() 方法为例吧,参考代码如下:
static void Main()
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"Triggering GC #{i}...");
GC.Collect();
Thread.Sleep(1000);
}
}
熟悉GC的朋友应该知道 GC.Collect() 下游方法是coreclr!WKS::GCHeap::GarbageCollect(),我想在命中这个方法的时候执行一点我的自定义逻辑,这里有一点注意的是,钩子的回调不要回调到 C#,最好采用 SlideCar 的方式,这里使用静态链接,C代码参考如下:
#include <windows.h>
#include <stdio.h>
#include <MinHook.h>
// 1. 使用 extern "C" 防止名称修饰
#ifdef __cplusplus
extern "C" {
#endif
// 2. 定义原始函数类型
typedef int(__fastcall* Real_GarbageCollect)(void* pThis, int generation, bool lowMemory, int mode);
// 3. 导出函数声明
__declspec(dllexport) BOOL WINAPI InstallGCHook();
__declspec(dllexport) void WINAPI UninstallGCHook();
#ifdef __cplusplus
}
#endif
// 4. 全局变量
static Real_GarbageCollect fpOriginalGarbageCollect = NULL;
static void* pTargetFunction = NULL;
// 5. 获取 coreclr.dll 中的函数地址(关键修改点)
static void* GetGCDunctionAddress() {
HMODULE hCoreCLR = GetModuleHandleW(L"coreclr.dll");
if (!hCoreCLR) {
printf("[ERROR] coreclr.dll not loaded\n");
return NULL;
}
// 计算目标地址
return (BYTE*)hCoreCLR + 0x30E670; // 替换为你的实际偏移量
}
// 6. Detour 函数(保持不变)
int __fastcall Hook_GarbageCollect(void* pThis, int generation, bool lowMemory, int mode) {
printf("[GC Hook] this=0x%p, gen=%d, lowMem=%d, mode=%d\n",
pThis, generation, lowMemory, mode);
if (fpOriginalGarbageCollect) {
MH_DisableHook(pTargetFunction);
int result = fpOriginalGarbageCollect(pThis, generation, lowMemory, mode);
MH_EnableHook(pTargetFunction);
return result;
}
return 0;
}
// 7. 安装Hook(改为自动计算地址)
__declspec(dllexport) BOOL WINAPI InstallGCHook() {
pTargetFunction = GetGCDunctionAddress();
if (!pTargetFunction) return FALSE;
if (MH_Initialize() != MH_OK) {
printf("[ERROR] MinHook init failed\n");
return FALSE;
}
MH_STATUS status = MH_CreateHook(
pTargetFunction,
&Hook_GarbageCollect,
(void**)&fpOriginalGarbageCollect);
if (status != MH_OK) {
printf("[ERROR] CreateHook failed (status=0x%X)\n", status);
MH_Uninitialize();
return FALSE;
}
if (MH_EnableHook(pTargetFunction) != MH_OK) {
printf("[ERROR] EnableHook failed\n");
MH_Uninitialize();
return FALSE;
}
printf("[SUCCESS] Hook installed at 0x%p\n", pTargetFunction);
return TRUE;
}
// 8. 卸载Hook(保持不变)
__declspec(dllexport) void WINAPI UninstallGCHook() {
if (pTargetFunction) {
MH_DisableHook(pTargetFunction);
MH_RemoveHook(pTargetFunction);
}
MH_Uninitialize();
printf("[INFO] Hook uninstalled\n");
}
然后指定 头文件,链接文件,截图如下:


上面的 Hook_GarbageCollect 函数就是回调的地方,我用 printf 输出当前 GarbageCollect 参数信息, 接下来就是 C# 侧了,把生成好的 ConsoleApplication2.dll 丢到 C# 的 bin 目录下,参考代码如下:
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("ConsoleApplication2.dll", CallingConvention = CallingConvention.StdCall)]
public static extern bool InstallGCHook();
[DllImport("ConsoleApplication2.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void UninstallGCHook();
static void Main()
{
try
{
if (InstallGCHook())
{
Console.WriteLine("Hook installed. Press any key to exit...");
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"Triggering GC #{i}...");
GC.Collect();
Thread.Sleep(1000);
}
}
}
finally
{
UninstallGCHook();
}
}
}
最后运行程序,可以清楚的看到每次 GC.Collect() 都被成功拦截,截图如下:

如果你很想知道汇编层到底发生了什么变化,可以用 windbg 观察便知,截图如下,真的太完美了,经典的 jmp 跳转。

2. 相对偏移 0x30E670 的疑问
相信有不少人阅读代码之后,会对 return (BYTE*)hCoreCLR + 0x30E670; 中的 0x30E670 感兴趣,其实这条语句表示函数coreclr!WKS::GCHeap::GarbageCollect 的入口地址,其中的 0x30E670 偏移是怎么知道的呢? 我是用 windbg 观测的,计算如下:
0:000> lmvm coreclr
Browse full module list
start end module name
00007ff8`508c0000 00007ff8`50d9d000 coreclr (private pdb symbols)
...
0:000> x coreclr!WKS::GCHeap::GarbageCollect
00007ff8`50bce670 coreclr!WKS::GCHeap::GarbageCollect (int, bool, int)
0:000> ? 00007ff8`50bce670 - 00007ff8`508c0000
Evaluate expression: 3204720 = 00000000`0030e670
卦中的 000000000030e670 便是,相信此时又会有人提一个疑问,不同版本不同环境下的 coreclr 都可以用这个 0x30e670 吗?很显然这是不对的, 0x30e670 本质上是相对 模块 的偏移地址,同版本的coreclr是没有问题的,不同版本因为代码结构不一样,自然相对地址就不一样,所以大家需要根据生产环境的coreclr版本提前计算一下偏移值即可。
三:总结
借助 harmony,minhook 两大工具可以黑进三大代码领域 .netsdk,win32,coreclr,这在.NET高级调试体系下是一枚核武的存在,相信这篇文章也给这家 .NET公司 解决场景问题提供了一个思考点。

MinHook 如何对 .NET 母体 CoreCLR 进行拦截的更多相关文章
- CoreCLR源码探索(一) Object是什么
.Net程序员们每天都在和Object在打交道 如果你问一个.Net程序员什么是Object,他可能会信誓旦旦的告诉你"Object还不简单吗,就是所有类型的基类" 这个答案是对的 ...
- 6. ModelDriven拦截器、Preparable 拦截器
1. 问题 Struts2 的 Action 我们将它定义为一个控制器,但是由于在 Action 中也可以来编写一些业务逻辑,也有人会在 Action 输入业务逻辑层. 但是在企业开发中,我们一般会将 ...
- JS判断鼠标进入容器方向的方法和分析window.open新窗口被拦截的问题
1.鼠标进入容器方向的判定 判断鼠标从哪个方向进入元素容器是一个经常碰到的问题,如何来判断呢?首先想到的是:获取鼠标的位置,然后经过一大堆的if..else逻辑来确定.这样的做法比较繁琐,下面介绍两种 ...
- 【置顶】CoreCLR系列随笔
CoreCLR配置系列 在Windows上编译和调试CoreCLR GC探索系列 C++随笔:.NET CoreCLR之GC探索(1) C++随笔:.NET CoreCLR之GC探索(2) C++随笔 ...
- .NET CoreCLR开发人员指南(上)
1.为什么每一个CLR开发人员都需要读这篇文章 和所有的其他的大型代码库相比,CLR代码库有很多而且比较成熟的代码调试工具去检测BUG.对于程序员来说,理解这些规则和习惯写法非常的重要. 这篇文章让所 ...
- C++随笔:.NET CoreCLR之GC探索(4)
今天继续来 带大家讲解CoreCLR之GC,首先我们继续看这个GCSample,这篇文章是上一篇文章的继续,如果有不清楚的,还请翻到我写的上一篇随笔.下面我们继续: // Initialize fre ...
- C++随笔:.NET CoreCLR之GC探索(3)
有几天没写GC相关的文章了哈,今天我讲GC的方式是通过一个小的Sample来讲解,这个小的示例代码只有全部Build成功了才会有.地址为D:\coreclr2\coreclr\bin\obj\Wind ...
- C++随笔:从Hello World 探秘CoreCLR的内部(1)
紧接着上次的问题,上次的问题其实很简单,就是HelloWorld.exe运行失败,而本文的目的,就是成功调试HelloWorld这个控制台应用程序. 通过我的寻找,其实是一个名为TryRun的文件出了 ...
- 在Windows上编译和调试CoreCLR
生成CoreCLR - Windows篇 本文的唯一目的就是让你运行Hello World 运行环境 Window 7+ Visual studio 2015 确保C++ 工具已经被安装,默认是不安装 ...
- C++随笔:.NET CoreCLR之corleCLR核心探索之coreconsole(2)
这篇文章是上篇的续集,本文将会继续介绍coreconsole.cpp里面的逻辑.也许大家会看一些CLR的书,我承认我没有看过,因为我觉得一个人,他再NB,那也是他自己的眼光,而且说句难听的,CLR也不 ...
随机推荐
- datasnap的监督功能【3】-TCP链接监督功能
1.对于使用TCP/IP链接的客户端应用程序,是具有状态的.一直等到客户端完成服务请求后释放配置的资源.如何掉线了,那么服务器就是傻傻地等着,可能导致资源耗尽. 如何在服务端选择一个链接切断关闭之: ...
- datasnap的回调广播
感觉中的datasnap千孔百疮,到xe10已经具备冲击成千上万用户并发的能力了.应该放心用于项目实战了.补课研究10.1 datasnap开发手册. 用到的方法: (1)TDBXCallback机制 ...
- C#/.NET/.NET Core技术前沿周刊 | 第 32 期(2025年3.24-3.31)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...
- 第八届机械工程与应用复合材料国际会议(MEACM 2025)
第八届机械工程与应用复合材料国际会议(MEACM 2025) 吉隆坡,马来西亚 2025年8月25-27日 会议简介:2025年第八届机械工程与应用复合材料国际会议(MEACM 2025)将于2025 ...
- Go单元测试与报告
1.编写代码 1)打卡GoLand,新建项目命名为gotest 2)在gotest目录下新建两个go file,如下图所示: 其中CircleArea.go为计算圆面积的待测go程序,代码如下: pa ...
- 关于全球化大规模混合云 Kubernetes Prometheus 监控体系标准化及 GitOps 自动化改进方案
背景 现状 某司概况: PaaS/SaaS 公司,业务面向全球,包括 东南亚/南亚/中东/欧洲/非洲/美洲/东亚... 生产 k8s 集群数十套,生产非生产 >100 套(多种集群类型,各种公有 ...
- 安卓智能手机芯片上audio的bringup
基于安卓平台的智能手机芯片回来后要做bringup.首先是负责平台的把安卓OS起来,然后就轮到各功能模块做bringup了,Audio是其中主要功能模块之一.Audio由于场景多和牵涉到的core多, ...
- Web前端入门第 41 问:神奇的 transform 改变元素形状,matrix3d 矩阵变换算法演示
CSS transform 属性中文翻译过来是 变换,始终觉得翻译差那么一点意思.它可以用来改变元素形状,比如旋转.缩放.移动.倾斜等,就是它可以把元素各种拿捏~ transform 特性是在不改变自 ...
- windows11 安装CUDA Toolkit,Python,Anaconda,PyTorch并使用DeepSeek 多模态模型 Janus-Pro识别和生成图片
一.概述 因为公司网络做了严格限制,必须使用账号登录,才能上网.必须是指定的ip地址和MAC地址设备才可以上网. windows11开启热点,安装第三方虚拟机软件,开启WSL2虚拟机都是被禁止的,否则 ...
- java常用包的介绍
java.* java.lang 包含Java程序所需要的基本类(默认导入) java.util 包含丰富的常用工具类,如集合框架.事件模式.日期时间等 java.math ...