0

Please note that I have no advanced knowledge of code injection nor IL code treatment. In fact I have less than basic knowledge for this and I needed to research much time to write the code at the end of this post.


I'm under .NET 4.8, I've found this answer which I think it suggests that I could modify at runtime the IL code (of the method body) of a compiled method. That is what I would like to do.

In the body of a compiled method I would like to inject a Call OpCode, in this example at the very begining (as first instruction of the method body) to call another method, but I would like to be have flexibility to choose the position where to inject these new IL instructions.

The problem is that when I try to put all this in practice, and due my lack of experience in this matter, I end up getting an AccessViolationException with this error message:

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

I think probably I'm breaking the IL Code and that is corrupting the memory and maybe it's just a matter of fixing the order of the OpCodes, or maybe it could be a memory issue in caso of that I'm not properly writing into the unmanaged memory when patching the original IL Code.

How can I fix this problem?.

Here is the code that I'm using. In this example I'm tying to modify the method with name "TestMethod" to insert an instruction in its body to call the other method with name "LoggerMethod":

Imports System.Reflection
Imports System.Reflection.Emit

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)

        ' Using a DynamicMethod is just the way I found 
        ' for using ILGenerator to generate the wanted IL code to inject. 
        Dim dynMethod As New DynamicMethod("qwerty_dyn", Nothing, Type.EmptyTypes, restrictedSkipVisibility:=True)
        Dim ilGen As ILGenerator = dynMethod.GetILGenerator(streamSize:=16)
        ilGen.Emit(OpCodes.Call, loggerMethodInfo)
        ' ilGen.EmitCall(OpCodes.Call, loggerMethodInfo, Nothing)

        ILHelper.InjectILCode(testMethodInfo, GetIlAsByteArray2(dynMethod), position:=0)
        Me.TestMethod()

        ' testMethodInfo.Invoke(Me, BindingFlags.Default, Type.DefaultBinder, Type.EmptyTypes, Thread.CurrentThread.CurrentCulture)

    End Sub

End Class
Public NotInheritable Class ILHelper

    Private Sub New()
    End Sub

    Public Shared Sub InjectILCode(method As MethodInfo, newIlCode As Byte(), position As Integer)

        Dim body As MethodBody = method.GetMethodBody()
        Dim ilOpCodes As Byte() = body.GetILAsByteArray()

        If position < 0 Then
            Throw New ArgumentException($"Position must be equals or greater than zero.")
        End If

        If position > ilOpCodes.Length Then
            Throw New IndexOutOfRangeException($"Position {position} is greater than the IL byte array length ({ilOpCodes.Length}).")
        End If

        Dim newIlOpCodes((newIlCode.Length + ilOpCodes.Length) - 1) As Byte

        ' Add new IL instructions first.
        For i As Integer = 0 To newIlCode.Length - 1
            newIlOpCodes(i) = newIlCode(i)
        Next

        ' Continue with adding the original IL instructions.
        For i As Integer = 0 To ilOpCodes.Length - 1
            newIlOpCodes(position + newIlCode.Length + i) = ilOpCodes(i)
        Next

        ' This helps to visualize the byte array differences:
        Console.WriteLine($"Old IL Bytes: {String.Join(", ", ilOpCodes)}")
        Console.WriteLine($"NeW Il Bytes: {String.Join(", ", newIlOpCodes)}")

        Dim methodHandle As RuntimeMethodHandle = method.MethodHandle

        ' Makes sure the target method gets compiled. 
        ' https://reverseengineering.stackexchange.com/a/21014
        RuntimeHelpers.PrepareMethod(methodHandle)

        Dim hGlobal As IntPtr = Marshal.AllocHGlobal(newIlOpCodes.Length)
        Marshal.Copy(newIlOpCodes, 0, hGlobal, newIlOpCodes.Length)

        Dim methodPointer As IntPtr = methodHandle.GetFunctionPointer()
        Marshal.WriteIntPtr(methodPointer, hGlobal)

        Marshal.FreeHGlobal(hGlobal)

    End Sub

End Class
Public Module DynamicMethodExtensions

    <Extension>
    Public Function GetIlAsByteArray2(dynMethod As DynamicMethod) As Byte()

        Dim ilGen As ILGenerator = dynMethod.GetILGenerator()
        Dim fiIlStream As FieldInfo

        ' https://stackoverflow.com/a/4147132/1248295
        ' Conditional for .NET 4.x because DynamicILGenerator class derived from ILGenerator.
        If Environment.Version.Major >= 4 Then
            fiIlStream = ilGen.GetType().BaseType.GetField("m_ILStream", BindingFlags.Instance Or BindingFlags.NonPublic)
        Else ' This worked on .NET 3.5
            fiIlStream = ilGen.GetType().GetField("m_ILStream", BindingFlags.Instance Or BindingFlags.NonPublic)
        End If

        Return TryCast(fiIlStream.GetValue(ilGen), Byte())

    End Function

    ' THIS IS NOT WORKING FOR ME, ArgumentException IS THROWN.
    ' --------------------------------------------------------
    '<Extension>
    'Public Function GetIlAsByteArray(dynMethod As DynamicMethod) As Byte()
    '
    '    Dim resolver As Object = GetType(DynamicMethod).GetField("m_resolver", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(dynMethod)
    '    If resolver Is Nothing Then
    '        Throw New ArgumentException("The dynamic method's IL has not been finalized.")
    '    End If
    '    Return DirectCast(resolver.GetType().GetField("m_code", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(resolver), Byte())
    '
    'End Function

End Module
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • Check [Harmony](https://github.com/pardeike/Harmony) – Sohaib Jundi Jan 07 '23 at 16:09
  • @Sohaib Jundi Thanks for the suggestion but I would like to achieve this goal by learning and fixing the code that I shared, without depending on third party packages which also would force me to start somewhat from scratch. I really don't like the idea of using a heavy and full featured patching package just to be able to add a single IL instruction to a method body. – ElektroStudios Jan 07 '23 at 16:13
  • 1
    The stackexchange answer you are referencing suggests you can replace the x86 bytecode of an already (JIT-)compiled method with new x86 bytecode. As far as i can tell your code replaces the compiled x86 bytecode with IL code. Thats probably not what the runtime expects. Also, just adding IL code from one method to another will go wrong in all but the most trivial cases. There will be problems at least with variable slot numbers and types, exception handlers, return instructions. Have a look at the non-Harmony answers here for more ideas: https://stackoverflow.com/questions/7299097 – thehennyy Jan 11 '23 at 07:44
  • @thehennyy Thanks for the info. I was aware of that thread, I'd read all those answers and tried them before posting this bounty,including Harmony,but that kind of patching or method-redirection I think it is not suitable for me.I also tried the methodology based on ICLRProfiling interface on the codeproject's article from Olivier's answer in that thread (which in fact is the same methodology and article shown in the reverseengineering.stackexchange.com thread that I linked in my post),I think that's what I need,but I get a "CLRMissing" error msg always, after initialization. I'm not sure why. – ElektroStudios Jan 12 '23 at 08:15

0 Answers0