JIT 编译从 IL 到机器码的最后一步.NET 程序的执行流程IL 程序集 → CLR 加载 → JIT 编译器 (clrjit.dll)→ 逐方法编译 IL 为 x86/x64 机器码 → 执行clrjit.dll 导出的 getJit() 函数返回 ICorJitCompiler 接口其 vtable[0] 就是 compileMethod 函数指针。每个需要执行的方法在首次调用时都会经过 compileMethod。JIT Hook 的思路是在 compileMethod 被调用时把加密的 IL 替换回原始 IL让 JIT 编译器正常编译。Hook 原理替换 clrjit.dll 的 compileMethod// 伪代码流程Hook(){// 1. 加载 clrjit.dllvar jitModule LoadLibrary(“clrjit.dll”);// 2. 获取 getJit 导出var getJit GetProcAddress(jitModule, “getJit”);// 3. 调用 getJit() → 获取 ICorJitCompiler*var compiler ((delegate*)getJit)();// 4. 读取 vtable[0] compileMethod 原始指针var originalCompileMethod compiler[0];// 5. 创建 trampolinevar trampoline CreateTrampoline(HookHandler, originalCompileMethod);// 6. 修改内存保护 → 写入 trampoline → 替换 vtable[0]VirtualProtect(compiler, PAGE_EXECUTE_READWRITE);compiler[0] trampoline;}Trampoline在不破坏 vtable 的前提下跳转直接修改 vtable 在现代 Windows 上会被 DEP数据执行保护阻止。我们通过trampoline跳板实现Trampoline 结构x64:mov rax, HookHandler 地址 ; 48 B8 [8 bytes addr]jmp rax ; FF E0共 12 字节trampoline 通过 VirtualAlloc(MEM_COMMIT, PAGE_EXECUTE_READWRITE) 分配在可执行内存中。哨兵 IL用 7 个字节传递方法索引保护时为每个方法生成一条哨兵 IL——JIT Hook 可以检测到它并替换为真实 IL哨兵 IL7 字节:ldc.i4 ; 20 [4 bytes index] (实际上 index 嵌入在 IL 字节 1-4)pop ; 26ret ; 2A检测条件ILCodeSize 7 ILCode[0] 0x20 ILCode[5] 0x26 ILCode[6] 0x2Aint HookHandler(ICorJitCompiler* compiler, CORINFO_METHOD_INFO* methodInfo, …){// 检测哨兵 ILif (methodInfo-ILCodeSize 7 methodInfo-ILCode[0] 0x20 methodInfo-ILCode[5] 0x26 methodInfo-ILCode[6] 0x2A){// 从 IL 字节 1-4 读取方法索引uint methodIndex (uint)(methodInfo-ILCode 1);// 从加密数据中解密真实 IL byte[] realIL DecryptMethodData(methodIndex); // 替换 ILCode 指针和大小 methodInfo-ILCode realIL; methodInfo-ILCodeSize realIL.Length; } // 调用原始 compileMethod return originalCompileMethod(compiler, methodInfo, ...);}5. 方法数据存储与加密与虚拟化和方法体加密类似使用魔术头扫描定位[4 bytes] Magic: 0x4A495448 (“JITH”)[4 bytes] MethodCount[foreach method]:[4 bytes] MethodToken[4 bytes] ILCodeLength[N bytes] ILCode (加密)[4 bytes] LocalSigLength[N bytes] LocalSig (加密)6. x86 vs x64两种架构的 trampoline 实现x64 trampoline12 字节mov rax, 0x123456789ABCDEF0 ; 48 B8 [8 bytes addr]jmp rax ; FF E0x86 trampoline7 字节mov eax, 0x12345678 ; B8 [4 bytes addr]jmp eax ; FF E0两种架构的 trampoline 大小必须完整保存以便内存对齐和后续切换。限制与兼容性限制 说明仅 .NET Framework 4.5 ICorJitCompiler 在 .NET Core 中不再是导出接口仅支持 Windows clrjit.dll 是 Windows 专属v1 不支持异常处理器 含 EH 的方法自动跳过不支持泛型方法 类型参数在编译时才确定每个方法的 IL 大小限制 必须 哨兵 IL 大小的理论极限