You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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 _);
            }
        }
    }
}

关键修改说明

  1. 使用标准API获取函数入口:改用MethodHandle.GetFunctionPointer()获取方法的实际执行地址,避免直接依赖MethodHandle内部内存布局(这是.NET 8变更的核心影响点)。
  2. 添加内存权限修改:通过VirtualProtect将目标方法的代码页权限临时改为可读写可执行,解决.NET 8默认代码页只读导致的写入异常。
  3. 修正跳转指令实现:
    • x86平台保留相对跳转逻辑,但直接写入标准JMP指令,无需依赖原方法的初始指令结构。
    • x64平台改用绝对跳转方案(MOV RAX + JMP RAX),避免相对偏移的长度限制,同时适配新的代码布局。
  4. 移除硬编码偏移:删除原代码中对MethodHandle内部结构的硬编码偏移(如+1、+2),避免版本变更导致的兼容性问题。

注意事项

  • 仅适用于Windows平台,跨平台需替换VirtualProtect为对应系统的内存权限修改API(如Linux的mprotect、macOS的vm_protect)。
  • 目标方法和注入方法需标记[MethodImpl(MethodImplOptions.NoInlining)],防止编译器内联导致重定向失效。
  • 仅支持非泛型方法,泛型方法的JIT编译结构存在差异,需额外处理。
  • 项目需启用不安全代码(项目属性→生成→勾选"允许不安全代码")。

内容的提问来源于stack exchange,提问作者ysabih2

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 09:02:22