This is the eighteenth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack
Today we are going to see a rewritten way of hijacking method with machine code. It works in Windows and Linux, for both Debug and Release using .NET 5.
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
using System; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace MethodHijackerNetCore { class Program { static void Main(string[] args) { Console.WriteLine($"Calling StaticString method before hacking:\t{TestClass.StaticString()}"); HijackMethod(typeof(TestClass), nameof(TestClass.StaticString), typeof(Program), nameof(StaticStringHijacked)); Console.WriteLine($"Calling StaticString method after hacking:\t{TestClass.StaticString()}"); Console.WriteLine(); var instance = new TestClass(); Console.WriteLine($"Calling InstanceString method before hacking:\t{instance.InstanceString()}"); HijackMethod(typeof(TestClass), nameof(TestClass.InstanceString), typeof(Program), nameof(InstanceStringHijacked)); Console.WriteLine($"Calling InstanceString method after hacking:\t{instance.InstanceString()}"); Console.WriteLine(); Vector2 v = new Vector2(9.856331f, -2.2437377f); for (int i = 1; i <= 35 ; i++) { MultiTieredClass.Test(v, i); Thread.Sleep(100); } } [MethodImpl(MethodImplOptions.NoInlining)] public static string StaticStringHijacked() { return "Static string hijacked"; } [MethodImpl(MethodImplOptions.NoInlining)] public string InstanceStringHijacked() { return "Instance string hijacked"; } public static void HijackMethod(Type sourceType, string sourceMethod, Type targetType, string targetMethod) { var source = sourceType.GetMethod(sourceMethod); var target = targetType.GetMethod(targetMethod); RuntimeHelpers.PrepareMethod(source.MethodHandle); RuntimeHelpers.PrepareMethod(target.MethodHandle); var offset = 2 * IntPtr.Size; IntPtr sourceAddress = Marshal.ReadIntPtr(source.MethodHandle.Value, offset); IntPtr targetAddress = Marshal.ReadIntPtr(target.MethodHandle.Value, offset); var is32Bit = IntPtr.Size == 4; byte[] instruction; if (is32Bit) { instruction = new byte[] { 0x68, // push <value> } .Concat(BitConverter.GetBytes((int)targetAddress)) .Concat(new byte[] { 0xC3 //ret }).ToArray(); } else { instruction = new byte[] { 0x48, 0xB8 // mov rax <value> } .Concat(BitConverter.GetBytes((long)targetAddress)) .Concat(new byte[] { 0x50, // push rax 0xC3 // ret }).ToArray(); } Marshal.Copy(instruction, 0, sourceAddress, instruction.Length); } } class TestClass { [MethodImpl(MethodImplOptions.NoInlining)] public static string StaticString() { return "Static string"; } [MethodImpl(MethodImplOptions.NoInlining)] public string InstanceString() { return "Instance string"; } } class MultiTieredClass { [MethodImpl(MethodImplOptions.NoInlining)] public static void Test(Vector2 v, int i) { v = Vector2.Normalize(v); Console.WriteLine($"Vector iteration {i:0000}:\t{v}\t{TestClass.StaticString()}"); } } } |
Tested with .NET 5.0.102 on Windows and .NET 5.0.401 on WSL2 Ubuntu 20.04. This is the output:
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 |
Calling StaticString method before hacking: Static string Calling StaticString method after hacking: Static string hijacked Calling InstanceString method before hacking: Instance string Calling InstanceString method after hacking: Instance string hijacked Vector iteration 0001: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0002: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0003: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0004: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0005: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0006: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0007: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0008: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0009: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0010: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0011: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0012: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0013: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0014: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0015: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0016: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0017: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0018: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0019: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0020: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0021: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0022: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0023: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0024: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0025: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0026: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0027: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0028: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0029: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0030: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0031: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0032: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0033: <0.9750545, -0.22196561> Static string hijacked Vector iteration 0034: <0.97505456, -0.22196563> Static string Vector iteration 0035: <0.97505456, -0.22196563> Static string Examine MethodDescriptor: 7FFA3ACF5218 |
So we clearly see that the hacking works and that after multitiered compilation kicks in it no longer calls method but inlines it.