.NET 8运行时动态重定向C#方法的适配求助
.NET 8 运行时方法重定向代码适配方案
问题背景
原有用于运行时将C#方法重定向的代码在.NET 6中正常工作,但迁移到.NET 8后触发System.AccessViolationException内存访问异常。推测原因是.NET 8对函数指针反射相关机制做了重大变更,导致原代码依赖的方法句柄内存布局、代码页权限等发生变化。尝试调整指令偏移量未解决问题,且暂无合适替代方案,需适配.NET 8环境。
原代码如下:
public class Injection { public static void install(MethodInfo methodToReplace, MethodInfo methodToInject) { RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle); RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle); unsafe { if (IntPtr.Size == 4) { int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2; int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2; byte* injInst = (byte*)*inj; byte* tarInst = (byte*)*tar; int* injSrc = (int*)(injInst + 1); int* tarSrc = (int*)(tarInst + 1); *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5); } else { long* inj = (long*)methodToInject.MethodHandle.Value.ToPointer()+1; long* tar = (long*)methodToReplace.MethodHandle.Value.ToPointer()+1; byte* injInst = (byte*)*inj; byte* tarInst = (byte*)*tar; int* injSrc = (int*)(injInst + 1); int* tarSrc = (int*)(tarInst + 1); *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5); } } } }
适配后的代码
using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; public class Injection { [DllImport("kernel32.dll", SetLastError = true)] private static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); private const uint PAGE_EXECUTE_READWRITE = 0x40; public static void Install(MethodInfo methodToReplace, MethodInfo methodToInject) { // 确保方法已JIT编译 RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle); RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle); unsafe { // 获取方法的实际执行入口指针,避免依赖内部布局 IntPtr targetFuncPtr = methodToReplace.MethodHandle.GetFunctionPointer(); IntPtr injectFuncPtr = methodToInject.MethodHandle.GetFunctionPointer(); byte* targetCode = (byte*)targetFuncPtr.ToPointer(); byte* injectCode = (byte*)injectFuncPtr.ToPointer(); uint oldProtect; // 修改目标方法代码页权限为可读写可执行 if (!VirtualProtect(targetFuncPtr, (UIntPtr)(IntPtr.Size == 4 ? 5 : 12), PAGE_EXECUTE_READWRITE, out oldProtect)) { throw new System.ComponentModel.Win32Exception(); } try { if (IntPtr.Size == 4) // x86平台 { // 写入x86跳转指令:E9 + 4字节相对偏移 *targetCode = 0xE9; // JMP 相对地址 int* offsetPtr = (int*)(targetCode + 1); // 计算相对偏移:注入方法入口 - 当前指令下一条地址 *offsetPtr = (int)(injectCode - (targetCode + 5)); } else // x64平台 { // 写入x64绝对跳转指令:MOV RAX, 注入方法地址 → JMP RAX *targetCode = 0x48; // MOV RAX, imm64 *(targetCode + 1) = 0xB8; long* raxValuePtr = (long*)(targetCode + 2); *raxValuePtr = (long)injectCode; *(targetCode + 10) = 0xFF; // JMP RAX *(targetCode + 11) = 0xE0; } } finally { // 恢复原内存权限 VirtualProtect(targetFuncPtr, (UIntPtr)(IntPtr.Size == 4 ? 5 : 12), oldProtect, out _); } } } }
关键修改说明
- 使用标准API获取函数入口:改用
MethodHandle.GetFunctionPointer()获取方法的实际执行地址,避免直接依赖MethodHandle内部内存布局(这是.NET 8变更的核心影响点)。 - 添加内存权限修改:通过
VirtualProtect将目标方法的代码页权限临时改为可读写可执行,解决.NET 8默认代码页只读导致的写入异常。 - 修正跳转指令实现:
- x86平台保留相对跳转逻辑,但直接写入标准JMP指令,无需依赖原方法的初始指令结构。
- x64平台改用绝对跳转方案(MOV RAX + JMP RAX),避免相对偏移的长度限制,同时适配新的代码布局。
- 移除硬编码偏移:删除原代码中对MethodHandle内部结构的硬编码偏移(如
+1、+2),避免版本变更导致的兼容性问题。
注意事项
- 仅适用于Windows平台,跨平台需替换
VirtualProtect为对应系统的内存权限修改API(如Linux的mprotect、macOS的vm_protect)。 - 目标方法和注入方法需标记
[MethodImpl(MethodImplOptions.NoInlining)],防止编译器内联导致重定向失效。 - 仅支持非泛型方法,泛型方法的JIT编译结构存在差异,需额外处理。
- 项目需启用不安全代码(项目属性→生成→勾选"允许不安全代码")。
内容的提问来源于stack exchange,提问作者ysabih2
相关产品推荐
相关产品推荐

