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

.NET 4.8运行时注入IL代码触发AccessViolationException的解决方法

解决.NET 4.8运行时IL注入触发AccessViolationException的问题

问题背景

在.NET 4.8环境下尝试在已编译的实例方法TestMethod开头注入调用LoggerMethod的IL指令时,触发AccessViolationException,错误信息:

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

错误原因分析

  1. 混淆IL与JIT编译后的机器码:原代码直接将方法指针指向非托管内存中的IL字节数组,这是完全错误的——方法指针指向的是JIT编译后的x86/x64机器码,不是IL代码,直接替换会导致执行非法内存。
  2. 注入的IL指令栈不平衡:LoggerMethod是实例方法,调用时需要先将当前实例(this指针)压入评估栈,但原DynamicMethod生成的IL没有添加OpCodes.Ldarg_0指令,导致栈状态非法,触发内存错误。
  3. 内存释放时机错误:写入方法指针后立即释放非托管内存,方法执行时会访问已被回收的内存区域,直接触发保护内存访问错误。
  4. IL内存区域只读保护:.NET默认会将方法的IL内存标记为只读,直接写入会触发访问权限错误。

修复方案

  1. 生成正确的注入IL:调用实例方法前添加OpCodes.Ldarg_0压入当前实例。
  2. 修改IL内存的保护属性:使用Windows API将方法的IL内存区域改为可读写,修改完成后恢复只读权限。
  3. 直接修改原方法的IL内存:不需要替换方法指针,直接修改原方法的IL字节数组,避免内存释放问题。
  4. 修正IL注入的位置逻辑:原代码的数组拼接逻辑错误,需按「原IL前半段 + 新IL + 原IL后半段」的顺序拼接。

修正后的代码

Form1类

Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Runtime.CompilerServices

Public NotInheritable Class Form1 : Inherits Form

    Public Sub TestMethod()
        Console.WriteLine("Test Method Call.")
    End Sub

    Public Sub LoggerMethod(<CallerMemberName> Optional memberName As String = "")
        Console.WriteLine($"Logger method call by '{memberName}'.")
    End Sub

    Private Sub Form1_Shown(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Shown
        Dim testMethodInfo As MethodInfo = GetType(Form1).GetMethod("TestMethod", BindingFlags.Instance Or BindingFlags.Public)
        Dim loggerMethodInfo As MethodInfo = GetType(Form1).GetMethod("LoggerMethod", BindingFlags.Instance Or BindingFlags.Public)

        ' 生成正确的注入IL:先压入this,再调用实例方法
        Dim dynMethod As New DynamicMethod("inject_dyn", Nothing, {GetType(Form1)}, GetType(Form1), restrictedSkipVisibility:=True)
        Dim ilGen As ILGenerator = dynMethod.GetILGenerator()
        ilGen.Emit(OpCodes.Ldarg_0) ' 压入当前Form1实例
        ilGen.Emit(OpCodes.Call, loggerMethodInfo)

        Dim injectIL As Byte() = dynMethod.GetIlAsByteArray()
        ILHelper.InjectILCode(testMethodInfo, injectIL, position:=0)

        Me.TestMethod()
    End Sub

End Class

ILHelper工具类

Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.Runtime.CompilerServices

Public NotInheritable Class ILHelper

    Private Sub New()
    End Sub

    ' 导入Windows API修改内存保护属性
    <DllImport("kernel32.dll", SetLastError:=True)>
    Private Shared Function VirtualProtect(lpAddress As IntPtr, dwSize As UIntPtr, flNewProtect As UInteger, ByRef lpflOldProtect As UInteger) As Boolean
    End Function

    Private Const PAGE_READWRITE As UInteger = &H4
    Private Const PAGE_EXECUTE_READ As UInteger = &H20

    Public Shared Sub InjectILCode(method As MethodInfo, newIlCode As Byte(), position As Integer)
        Dim body As MethodBody = method.GetMethodBody()
        Dim originalIL As Byte() = body.GetILAsByteArray()

        ' 参数合法性检查
        If position < 0 OrElse position > originalIL.Length Then
            Throw New ArgumentOutOfRangeException(NameOf(position), "注入位置超出IL数组范围")
        End If

        ' 拼接新的IL数组:原IL[0..position-1] + 新IL + 原IL[position..end]
        Dim newIL As Byte() = New Byte(originalIL.Length + newIlCode.Length - 1) {}
        Array.Copy(originalIL, 0, newIL, 0, position)
        Array.Copy(newIlCode, 0, newIL, position, newIlCode.Length)
        Array.Copy(originalIL, position, newIL, position + newIlCode.Length, originalIL.Length - position)

        ' 输出IL字节对比
        Console.WriteLine($"原IL字节: {String.Join(", ", originalIL)}")
        Console.WriteLine($"新IL字节: {String.Join(", ", newIL)}")

        ' 获取方法的IL内存指针
        Dim methodHandle As RuntimeMethodHandle = method.MethodHandle
        RuntimeHelpers.PrepareMethod(methodHandle)

        ' 获取IL内存的起始地址和长度(.NET 4.8固定偏移)
        Dim ilHeader As IntPtr = methodHandle.Value + &H28 
        Dim ilSize As Integer = Marshal.ReadInt32(ilHeader - &H4) 

        ' 修改内存保护为可读写
        Dim oldProtect As UInteger
        VirtualProtect(ilHeader, CUInt(newIL.Length), PAGE_READWRITE, oldProtect)

        ' 写入新的IL代码并更新长度
        Marshal.Copy(newIL, 0, ilHeader, newIL.Length)
        Marshal.WriteInt32(ilHeader - &H4, newIL.Length)

        ' 恢复内存保护为只读可执行
        VirtualProtect(ilHeader, CUInt(newIL.Length), oldProtect, oldProtect)
    End Sub

End Class

DynamicMethod扩展方法

Imports System.Reflection
Imports System.Reflection.Emit

Public Module DynamicMethodExtensions

    <Extension>
    Public Function GetIlAsByteArray(dynMethod As DynamicMethod) As Byte()
        ' 获取DynamicMethod的IL字节数组(适用于.NET 4.x)
        Dim resolverField As FieldInfo = GetType(DynamicMethod).GetField("m_resolver", BindingFlags.Instance Or BindingFlags.NonPublic)
        Dim resolver As Object = resolverField.GetValue(dynMethod)
        If resolver Is Nothing Then
            Throw New InvalidOperationException("DynamicMethod的IL尚未完成生成")
        End If

        Dim codeField As FieldInfo = resolver.GetType().GetField("m_code", BindingFlags.Instance Or BindingFlags.NonPublic)
        Return DirectCast(codeField.GetValue(resolver), Byte())
    End Function

End Module

注意事项

  • 不同.NET版本的方法元数据偏移可能不同,.NET 4.8中IL头部偏移为0x28,其他版本需自行调整。
  • 该IL注入方式属于非官方操作,可能被.NET安全机制拦截,仅适合调试或内部工具场景。
  • 若方法已被JIT编译,修改IL后需强制重新JIT(可通过重启应用或调用RuntimeHelpers.PrepareMethod重置编译状态)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 04:20:31