This is the twentieth third 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#
.NET Core introduced tiered compilation and reworked AOT compilation. Previously, we could get address of machine code by calling GetFunctionPointer
on a method handle. However, it didn’t work for NGEN-ed methods and doesn’t work for ReadyToRun code or tiered methods. How to do it?
If you go through coreclr repository you’ll find this method:
1 |
PCODE MethodDesc::GetNativeCode() |
If we call it from C# we’ll be able to get the machine code address. Let’s do it:
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 |
using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace MachineCodeAddress { delegate int CallMethodReturnPointer(); class Program { static void Main(string[] args) { // Run as x86 // .load C:\users\afish\desktop\tools\sos\x86\sos.dll // x coreclr!MethodDesc::GetNativeCode // Gives // 79c6be0c coreclr!MethodDesc::GetNativeCode (void) // Replace address below with real one (or automate extracting it from coreclr.pdb + coreclr location in runtime) var methodDescriptorGetNativeCode_nativeAddress = 0x7c747dbb; var methodHandle = typeof(FileStream).GetMethod("WriteFileNative", BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle; RuntimeHelpers.PrepareMethod(methodHandle); var mainHandle = (int)methodHandle.Value; Console.ReadLine(); var getCodeAddress_code = new byte[] { 0xb9, (byte)(mainHandle & 0xFF), (byte)((mainHandle >> 8) & 0xFF), (byte)((mainHandle >> 16) & 0xFF), (byte)((mainHandle >> 24) & 0xFF), // b9 44 33 22 11 mov ecx,0x11223344 0x68 , (byte)(methodDescriptorGetNativeCode_nativeAddress & 0xFF), (byte)((methodDescriptorGetNativeCode_nativeAddress >> 8) & 0xFF), (byte)((methodDescriptorGetNativeCode_nativeAddress >> 16) & 0xFF), (byte)((methodDescriptorGetNativeCode_nativeAddress >> 24) & 0xFF),// 68 bb 7d 74 7c push 0x7c747dbb 0xc3, // c3 ret 0xc3, // c3 ret }; CallMethodReturnPointer getCodeAddress = FuncGenerator.Generate<CallMethodReturnPointer>(getCodeAddress_code); var codeAddress = getCodeAddress(); // These two should be different for ReadyToRun code Console.WriteLine($"CodeAddress: {codeAddress.ToString("X")}"); Console.WriteLine($"FunctionPointer: {methodHandle.GetFunctionPointer().ToString("X")}"); Console.ReadLine(); } } // Flags for VirtualProtect method public enum Protection { PAGE_NOACCESS = 0x01, PAGE_READONLY = 0x02, PAGE_READWRITE = 0x04, PAGE_WRITECOPY = 0x08, PAGE_EXECUTE = 0x10, PAGE_EXECUTE_READ = 0x20, PAGE_EXECUTE_READWRITE = 0x40, PAGE_EXECUTE_WRITECOPY = 0x80, PAGE_GUARD = 0x100, PAGE_NOCACHE = 0x200, PAGE_WRITECOMBINE = 0x400 } public class FuncGenerator { // Method to unlock page for executing [DllImport("kernel32.dll", SetLastError = true)] static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect); // Unlocks page for executing private static void UnlockPage(int address) { uint old; VirtualProtect((IntPtr)address, 6, (uint)Protection.PAGE_EXECUTE_READWRITE, out old); } // Some internal storage for pinning private static IList<object> memory = new List<object>(); private static IList<GCHandle> handles = new List<GCHandle>(); // Pins array with code and returns address to the beginning of the array private static IntPtr Pin(object data) { memory.Add(data); var handle = GCHandle.Alloc(data); handles.Add(handle); return Marshal.ReadIntPtr(GCHandle.ToIntPtr(handle)); } // Returns delegate of type T using class U for stubbing public static T Generate<T>(byte[] data) { // Address of machine code in array // We omit first 8 bytes (array type and size) var arrayCodeAddress = ((int)Pin(data)) + 8; // Unlock page so we can execute code from it UnlockPage(arrayCodeAddress); Console.WriteLine("Machine code in array address: " + arrayCodeAddress.ToString("X")); // Returns delegate of correct type so we have runtime type checking return (T)(object)Marshal.GetDelegateForFunctionPointer<T>((IntPtr)arrayCodeAddress); } } } |
General idea is we want to generate some machine code to call the method directly. To do that, we use .NET Inside Out Part 9 — Generating Func from a bunch of bytes in C# revisited approach. In lines 31-34 we pass the method descriptor via ecx register (the this
pointer). Then use the trick to push address and return to it which is effectively a call to an absolute address. Finally, we return the value of the eax register. Rest of the code is explained in other part.
You can run this on some ReadyToRun method (like in the example) and see that value returned by internal method differs from the value returned by .NET API.
It works, however, we need to know the physical address of the MethodDesc::GetNativeCode()
method. For the purpose of this demo I just extracted it with WinDBG, however, you can automate it in many ways. Keep in mind that the address will change between applications and system restarts.