This is the fourth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#
A word of warning! Things presented here depend on the implementation details and require an extensive knowledge about the internals. There are multiple things in place which may change the outcome — multi-tiered compilation, optimizations, JIT vs AoT compilation, and others. I wrote an article covering some aspects but YMMV.
Clickbait title once again. We have already seen how to override sealed function which we did by performing jump to other method. Today we are going to do that by rewriting CLR metadata.
Method descriptor
Each method descriptor contains address of function machine code. In theory nothing stops us from rewriting this value to point to different code. See the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace MethodHijacker2 { public static class MethodHijacker { public static void HijackMethod(MethodBase source, MethodBase target) { RuntimeHelpers.PrepareMethod(source.MethodHandle); RuntimeHelpers.PrepareMethod(target.MethodHandle); var sourceAddress = source.MethodHandle.Value; var targetAddress = (long)target.MethodHandle.GetFunctionPointer(); Marshal.WriteInt32(sourceAddress, 8, (int)targetAddress); } } class TestClass { public static string ReturnString() { return "Original string"; } public static string ReturnStringHijacked() { return "Modified string"; } public static string StringProperty { get; set; } public string NonStaticReturnStringHijacked() { return "Nonstatic modified string"; } public string NonStaticStringProperty { get; set; } } class Program { static void Main() { // String method Console.WriteLine(TestClass.ReturnString()); MethodHijacker.HijackMethod(typeof(TestClass).GetMethod(nameof(TestClass.ReturnString)), typeof(TestClass).GetMethod(nameof(TestClass.ReturnStringHijacked))); Console.WriteLine(TestClass.ReturnString()); // String property TestClass.StringProperty = "Test"; Console.WriteLine(TestClass.StringProperty); MethodHijacker.HijackMethod(typeof(TestClass).GetProperty(nameof(TestClass.StringProperty)).GetMethod, typeof(TestClass).GetMethod(nameof(TestClass.ReturnStringHijacked))); Console.WriteLine(TestClass.StringProperty); // Nonstatic property var instance = new TestClass(); instance.NonStaticStringProperty = "Test nonstatic"; Console.WriteLine(instance.NonStaticStringProperty); MethodHijacker.HijackMethod(typeof(TestClass).GetProperty(nameof(TestClass.NonStaticStringProperty)).GetMethod, typeof(TestClass).GetMethod(nameof(TestClass.NonStaticReturnStringHijacked))); Console.WriteLine(instance.NonStaticStringProperty); } } } |
It works with ordinary methods, doesn’t work with ngened ones – they don’t have address stored in method descriptor.