Click here to Skip to main content
15,861,125 members
Articles / Desktop Programming / Win32

CLR Injection: Runtime Method Replacer

Rate me:
Please Sign up or sign in to vote.
4.99/5 (44 votes)
23 Jun 2009CPOL8 min read 256K   4.9K   156   59
Replace any method with another method at runtime. Updated for 3.5 SP1.

Introduction

I have always been interested in the internal workings of the CLR. One thing of particular interest is the Just In Time Compiler or JIT. Today, we are going to look at how the JIT compiles MSIL, and create a utility that allows us to programmatically replace any JIT'ed method with another method at runtime. We will also create a debugging utility that will intercept JIT calls and print diagnostics information to the console.

Just In Time Compiler

Microsoft Intermediate Language or MSIL (properly know as Common Intermediate Language (CIL)) is a low level assembly type language. All .NET languages are compiled into MSIL (some exception with C++/CLI). Processors cannot run MSIL directly (maybe an ARM Jazelle like technology for .NET in the future). The Just In Time compiler is used to turn MSIL into machine code. A method will only be compiled once by the JIT, the CLR will cache the machine code the JIT outputs for future calls.

The compilation process needs to be extremely fast since it happens at runtime. Because MSIL is a low level language, its OP codes translate very easily to machine specific OP codes. The compilation process itself uses something called a JITStub. A JITStub is a chunk of machine code, each method will have one. The JIT stub initially contains code that will invoke the JIT for the method. After the method is JIT'ed, the stub is replaced with code that calls the machine code the JIT created directly.

MSIL
000EFA60 E86488D979       call        79E882C9  // Before JIT.  Call to JIT method

000EFA60 E97BC1CB00       jmp         00DABBE0 // After JIT.  Jmp to JIT'ed assembly code

Every class has a method table. A method table has the address of all the JITStubs for a class' methods. The method table is used at JIT time to resolve method JIT stubs. A method that has already been JIT'ed will not reference this table; the machine code created by the JIT will call the stub address directly. Below is the method table output from SOS. The entry column contains the stub address.

MethodDesc Table
   Entry MethodDesc      JIT Name
79371278   7914b928   PreJIT System.Object.ToString()
7936b3b0   7914b930   PreJIT System.Object.Equals(System.Object)
7936b3d0   7914b948   PreJIT System.Object.GetHashCode()
793624d0   7914b950   PreJIT System.Object.Finalize()
000efa60   00d77ce0      JIT ReplaceExample.StaticClassB.A()
000efa70   00d77ce8     NONE ReplaceExample.StaticClassB.B()

It is possible to invoke the JIT from managed code without actually invoking a method. The System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod method will force the JIT to compile the method.

Debugging

Debugging the JIT process can be quite difficult. Luckily, there are a number of great tools we can use. We will briefly go over them.

Debugging Using SOS

SOS stands for Son of Strike. It is a debugging extension for the CLR that you can use in Visual Studio or WinDbg. It is a really neat tool that comes with the .NET framework. I have used it several times to debug .NET exceptions on machines that don't have Visual Studio installed. We can also use it to locate and view structures in memory used by the CLR, view assembly and IL, and many other things. It has been my primary debugging tool for writing this. SOS only works when unmanaged debugging is enabled. For more info, please click here.

Rotor

Rotor is an Open Source (released under Microsoft Shared Source license) CLR released by Microsoft. Rotor is not the same as the CLR that Microsoft ships, but it is a complete CLR. The amount of code is enormous, and it can be quite difficult to find what you are looking for. We are using some of the headers from Rotor for our JIT Logger.

JIT Logger

The JITLogger is a tool that logs JIT calls to the console. It can be enabled and disabled. I used Daniel Pistelli's code from his .NET Internals and Code Injection article to create the JITLogger. Below are the JITLogger signatures and some sample output. For more information, please look at the attached code or read Daniel's excellent article.

C#
public class JitLogger
{
    public static bool Enabled { get; set; }
    public static int JitCompileCount { get; }
}

Output:

JIT :   0xdd20a8 Program.StaticTests
JIT :   0xdd217b Program.TestStaticReplaceJited
JIT :   0x749c205c MethodUtil.ReplaceMethod
JIT :   0x749c2270 MethodUtil.MethodSignaturesEqual
JIT :   0x749c231c MethodUtil.GetMethodReturnType
JIT :   0x749c20e4 MethodUtil.GetMethodAddress
JIT :   0xdd23e6 StaticClassB.A

Method Injection

I want the replace code to be very basic. We will take two methods, a source and destination, and replace the destination with the source. Below is the signature of our replacement method:

C#
public static void ReplaceMethod(MethodBase source, MethodBase dest)

IL Injection

I originally wanted to replace the IL, but I ran into some issues. The CLR seems to behave different depending on the build mode and if a debugger is attached. Also, the JIT calls are cached (stubs replaced). We would need to get the CLR to somehow invalidate the JIT cache. I was able to get this working, but only in debug mode, and I had to persist some state for each method to be able to get the CLR to re-JIT it.

I also tried using Daniel Pistelli's hooking method, but ran into some issues. I wanted to programmatically replace methods from managed code. I thought I could hook the JIT, use RuntimeHelpers.PrepareMethod to cause the method to get JITed, and then modify the CORINFO_METHOD_INFO structure that gets passed into our hooked method. Passing state between managed and unmanaged hooked methods was an issue. If we call any managed method from the hooked method, we get a stack overflow. Also, invalidating the JIT cache is a pain, and again, I could only get it working in debug mode.

Another approach I attempted was using the unmanaged metadata APIs. I would read the RVA from the metadata method table, use the RVA and module base address to find the IL address in memory, and just write over it. This was problematic because the length of the source IL in bytes must be less than the detestation. There is more than just the IL. We also have the tiny or fat IL header and possibly SEH structures, etc. After the method is invoked, once the JITStub is replaced, we run into the same problem as the other method.

After running into a wall with the different IL approaches, I decided to try a different method. Instead of replacing the IL, we will replace the assembly code that JIT outputs. With this approach, we don't have to worry about invaliding the cache, IL headers, SEH, etc.

Post JIT Injection

Our new approach will ensure both the source and destination methods are compiled, locate the method table in memory for both methods, and replace the destination's JITStub address with that of the source. We can replace a method as many times as we like, and do not have to worry about the method being cached. We need to locate a couple things in memory first.

We are going to use the RuntimeTypeHandle and RuntimeMethodHandle to locate the method table and a method slot in memory. The RuntimeMethodHandle points to an 8 byte structure in memory called a MethodDescription. This is the same address we see in the MethodDesc column using the SOS !DumpMT -MD command. This structure contains the index of the method in the method table. We can then use RuntimeTypeHandle to locate the method table itself. The method table starts 40 bytes after the RuntimeTypeHandle address.

Dynamic methods work differently. I could not really find any documentation, but I was able to find the JITStub address using the memory debugger. A dynamic method does not expose its RuntimeMethodHandle so we need to use Reflection to get it. I found the JITStub address 24 bytes after the address of the runtime method handle.

C#
public static IntPtr GetMethodAddress(MethodBase method)
{
    if ((method is DynamicMethod))
    {
        unsafe
        {
            byte* ptr = (byte*)GetDynamicMethodRuntimeHandle(method).ToPointer();
            if (IntPtr.Size == 8)
            {
                ulong* address = (ulong*)ptr;
                address += 6;
                return new IntPtr(address);
            }
            else 
            {
                uint* address = (uint*)ptr;
                address += 6;
                return new IntPtr(address);
            }
        }
    }

    RuntimeHelpers.PrepareMethod(method.MethodHandle);

    unsafe
    {
        // Some dwords in the met
        int skip = 10;

        // Read the method index.
        UInt64* location = (UInt64*)(method.MethodHandle.Value.ToPointer());
        int index = (int)(((*location) >> 32) & 0xFF);

        if (IntPtr.Size == 8)
        {
            // Get the method table
            ulong* classStart = (ulong*)method.DeclaringType.TypeHandle.Value.ToPointer();
            ulong* address = classStart + index + skip;
            return new IntPtr(address);
        }
        else
        {
            // Get the method table
            uint* classStart = (uint*)method.DeclaringType.TypeHandle.Value.ToPointer();
            uint* address = classStart + index + skip;
            return new IntPtr(address);
        }
    }
}

private static IntPtr GetDynamicMethodRuntimeHandle(MethodBase method)
{
    if (method is DynamicMethod)
    {
        FieldInfo fieldInfo = typeof(DynamicMethod).GetField("m_method", 
                              BindingFlags.NonPublic|BindingFlags.Instance);
        return ((RuntimeMethodHandle)fieldInfo.GetValue(method)).Value;
    }
    return method.MethodHandle.Value;
}

After we get the location of the JITStub addresses, we simply need to change the value. Shown below is our replace method:

C#
public static void ReplaceMethod(IntPtr srcAdr, MethodBase dest)
{
    IntPtr destAdr = GetMethodAddress(dest);
    unsafe
    {
        if (IntPtr.Size == 8)
        {
            ulong* d = (ulong*)destAdr.ToPointer();
            *d = *((ulong*)srcAdr.ToPointer());
        }
        else
        {
            uint* d = (uint*)destAdr.ToPointer();
            *d = *((uint*)srcAdr.ToPointer());
        }
    }
}
public static void ReplaceMethod(MethodBase source, MethodBase dest)
{
    if (!MethodSignaturesEqual(source, dest))
    {
        throw new ArgumentException("The method signatures are not the same.", 
                                    "source");
    }
    ReplaceMethod(GetMethodAddress(source), dest);
}

Example Code

In our example code, we are going to try several things. We are going to replace a static method from one class with a static method from another class. We will do the same thing with an instance method. We will also replace a static method with a DynamicMethod. Some of our test methods were being inlined in Release mode. I had to add MethodImpl attributes to several of the methods to prevent inlining.

If we step through the code, we will notice that Visual Studio gets tricked too. After the method is replaced, Visual Studio will step into the new method instead of the old method.

Below is the output from our tests:

Enabling JIT debugging.
        JIT :   0x10720a8 Program.StaticTests
        JIT :   0x107217b Program.TestStaticReplaceJited
Replacing StaticClassA.A() with StaticClassB.A()
        JIT :   0x71ac205c MethodUtil.ReplaceMethod
        JIT :   0x71ac2270 MethodUtil.MethodSignaturesEqual
        JIT :   0x71ac231c MethodUtil.GetMethodReturnType
        JIT :   0x71ac20e4 MethodUtil.GetMethodAddress
        JIT :   0x10723e6 StaticClassB.A
        JIT :   0x71ac2094 MethodUtil.ReplaceMethod
        JIT :   0x1072426 StaticClassA.A
Call StaticClassA.A() from a  method that has already been jited
StaticClassA.A
Call StaticClassA.A() from a  method that has not been jited
        JIT :   0x1072172 Program.TestStaticReplace
StaticClassB.A
        JIT :   0x1072190 Program.InstanceTests
        JIT :   0x1072284 Program.TestInstanceReplaceJited
Replacing InstanceClassA.A() with InstanceClassB.A()
        JIT :   0x10723c2 InstanceClassB.A
        JIT :   0x1072402 InstanceClassA.A
Call InstanceClassA.A() from a  method that has already been jited
        JIT :   0x107241e InstanceClassA..ctor
InstanceClassA.A
Call InstanceClassA.A() from a  method that has not been jited
        JIT :   0x1072268 Program.TestInstanceReplace
InstanceClassB.A
        JIT :   0x10722a0 Program.DynamicTests
        JIT :   0x1072344 Program.CreateTestMethod
Created new dynamic metbod StaticClassA.C
        JIT :   0x107232e Program.TestDynamicReplaceJited
Replacing StaticClassA.B() with dynamic StaticClassA.C()
        JIT :   0x71ac2210 MethodUtil.GetDynamicMethodRuntimeHandle
        JIT :   0x1072434 StaticClassA.B
Call StaticClassA.B() from a  method that has already been jited
StaticClassA.B
Call StaticClassA.B() from a  method that has not been jited
        JIT :   0x1072325 Program.TestDynamicReplace
        JIT :   0x10c318 DynamicClass.C
StaticClassA.C

Conclusions

The practical uses of this code are limited. If you want to modify a library you are using and don't have access to the source code, don't want to decompile recompile or use a hex editor, then this might be helpful to you. Might be possible to create some AOP library that modifies existing types at runtime instead of creating wrappers or build time approaches.

There are some limitations with this code. As we can see from the example code, once a method has been JIT'ed, it will no longer refer to the method table address we are changing. Replacement should happen before a calling method gets JIT'ed. This has not been tested at all on an x86-64 machine. Zapped or NGen-ed assemblies also do not work.

We need to keep in mind that we are directly manipulating the CLR memory in ways not intended. This code might not work with newer versions of the .NET framework. This was tested with .NET 3.5 on a Vista x86, and might not work on your machine.

It might be cool to write a class that could detect the processor features and reemit a more optimized version that takes advantages of technologies such as SIMD. My knowledge of the x86 assembler is a little bit limited. I will see if I can throw something together later though.

Update

It looks like the memory layout changed in .NET 2.0 SP2 which I was forced to install when installing .NET 3.5 SP1. I updated the code to detect the framework and act appropriately. Please let me know if there are any issues: ziadelmalki@hotmail.com.

Links

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWrapping with this method? Pin
Marvin_Brouwer19-Feb-22 8:08
Marvin_Brouwer19-Feb-22 8:08 
BugReplacing methods of interface implementation Pin
mgeramb15-Oct-19 23:16
mgeramb15-Oct-19 23:16 
QuestionConstructors Pin
Member 41379011-Mar-16 1:01
Member 41379011-Mar-16 1:01 
QuestionHow to replace a method for which there are calls from already JIT'ed methods Pin
alexaro1Cx11-Nov-15 4:18
alexaro1Cx11-Nov-15 4:18 
GeneralGreate Pin
Aladár Horváth16-Aug-15 22:37
professionalAladár Horváth16-Aug-15 22:37 
QuestionThis is an AWESOME PIECE! Pin
ziffwong11-Aug-15 1:20
ziffwong11-Aug-15 1:20 
QuestionHow do you obtain offsets? Pin
Member 1132359422-Feb-15 9:02
Member 1132359422-Feb-15 9:02 
Questiontips Pin
Member 107838535-Jul-14 6:44
Member 107838535-Jul-14 6:44 
AnswerRe: tips Pin
ziffwong11-Aug-15 3:15
ziffwong11-Aug-15 3:15 
QuestionMono Runtime Compatibility Pin
Member 107154691-Apr-14 2:06
Member 107154691-Apr-14 2:06 
QuestionRe: Mono Runtime Compatibility Pin
cysto7-Apr-15 0:26
cysto7-Apr-15 0:26 
QuestionHow to use this with overridden/virtual methods? Pin
Member 1062850626-Feb-14 12:34
Member 1062850626-Feb-14 12:34 
QuestionHow can I change a method in a class if the method is part of implementation of an interface? Pin
Subrahmanya Oruganti23-Jul-13 13:15
Subrahmanya Oruganti23-Jul-13 13:15 
QuestionGetting access violation with dynamic methods Pin
Kerem Kat27-Nov-12 6:00
Kerem Kat27-Nov-12 6:00 
QuestionGeneric Methods? Pin
RStern12-Jul-12 8:41
RStern12-Jul-12 8:41 
AnswerRe: Generic Methods? Pin
levitation9-Sep-12 8:10
levitation9-Sep-12 8:10 
GeneralRe: Generic Methods? Pin
Erich Wang17-Sep-12 22:52
Erich Wang17-Sep-12 22:52 
GeneralRe: Generic Methods? Pin
levitation28-Sep-12 5:24
levitation28-Sep-12 5:24 
GeneralRe: Generic Methods? Pin
Erich Wang4-Nov-12 20:13
Erich Wang4-Nov-12 20:13 
GeneralRe: Generic Methods? Pin
levitation11-Nov-12 4:52
levitation11-Nov-12 4:52 
GeneralRe: Generic Methods? Pin
wangchengh2-Mar-14 21:32
wangchengh2-Mar-14 21:32 
QuestionRe: Generic Methods? Pin
Robin Alden30-Oct-14 17:28
Robin Alden30-Oct-14 17:28 
GeneralRe: Generic Methods? Pin
spunkyvt16-Feb-15 14:20
spunkyvt16-Feb-15 14:20 
GeneralDirect generic method replacement doesn't work properly. Pin
ziffwong11-Aug-15 3:06
ziffwong11-Aug-15 3:06 
GeneralRe: Direct generic method replacement doesn't work properly. Pin
Senthilkumar Elangovan16-Aug-16 1:56
Senthilkumar Elangovan16-Aug-16 1:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.