.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.
错误原因分析
- 混淆IL与JIT编译后的机器码:原代码直接将方法指针指向非托管内存中的IL字节数组,这是完全错误的——方法指针指向的是JIT编译后的x86/x64机器码,不是IL代码,直接替换会导致执行非法内存。
- 注入的IL指令栈不平衡:
LoggerMethod是实例方法,调用时需要先将当前实例(this指针)压入评估栈,但原DynamicMethod生成的IL没有添加OpCodes.Ldarg_0指令,导致栈状态非法,触发内存错误。 - 内存释放时机错误:写入方法指针后立即释放非托管内存,方法执行时会访问已被回收的内存区域,直接触发保护内存访问错误。
- IL内存区域只读保护:.NET默认会将方法的IL内存标记为只读,直接写入会触发访问权限错误。
修复方案
- 生成正确的注入IL:调用实例方法前添加
OpCodes.Ldarg_0压入当前实例。 - 修改IL内存的保护属性:使用Windows API将方法的IL内存区域改为可读写,修改完成后恢复只读权限。
- 直接修改原方法的IL内存:不需要替换方法指针,直接修改原方法的IL字节数组,避免内存释放问题。
- 修正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
相关产品推荐
相关产品推荐

