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#

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:

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.