3

Already asked this question. How to replace the pointer to the overridden (virtual) method in the pointer of my method? (Release x64 and x86) Thanks @Machine Learning, solved the problem. But a new problem arose. If the system inherited from the class, such as "Systems.Windows.Forms", then the change does not work. Example:

using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows.Forms;

namespace ReplaceHandles
{
    public class Target1 : UserControl
    {
        public void test()
        {
            Console.WriteLine("Target1.test()");
        }
    }

    public class Target2
    {
        public void test()
        {
            Console.WriteLine("Target2.test()");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Injection.Replace();
            var target = new Target1();
            target.test();
            Console.Read();
        }
    }
}

Class that replaces pointers

    public class Injection
    {
        public static void Replace()
        {
            var methodToReplace = typeof(Target1).GetMethod("test", BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            var methodToInject = typeof(Target2).GetMethod("test", BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
            if (methodToReplace.IsVirtual) ReplaceVirtualInner(methodToReplace, methodToInject);
            else ReplaceInner(methodToReplace, methodToInject);
        }

Replacing the virtual methods

        static void ReplaceVirtualInner(MethodInfo methodToReplace, MethodInfo methodToInject)
        {
            unsafe
            {
                var methodDesc = (UInt64*)(methodToReplace.MethodHandle.Value.ToPointer());
                var index = (int)(((*methodDesc) >> 32) & 0xFF);
                if (IntPtr.Size == 4)
                {
                    if (methodToReplace.DeclaringType != null)
                    {
                        var classStart = (uint*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer();
                        classStart += 10;
                        classStart = (uint*)*classStart;
                        var tar = classStart + index;

                        var inj = (uint*)methodToInject.MethodHandle.Value.ToPointer() + 2;
#if DEBUG

                        var injInst = (byte*)*inj;
                        var tarInst = (byte*)*tar;
                        var injSrc = (int*)(injInst + 1);
                        var tarSrc = (int*)(tarInst + 1);
                        *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
                        *tar = *inj;
#endif
                    }
                }
                else
                {
                    if (methodToReplace.DeclaringType != null)
                    {
                        var classStart = (ulong*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer();
                        classStart += 8;
                        classStart = (ulong*)*classStart;
                        var tar = classStart + index;

                        var inj = (ulong*)methodToInject.MethodHandle.Value.ToPointer() + 1;
#if DEBUG
                        var injInst = (byte*)*inj;
                        var tarInst = (byte*)*tar;
                        var injSrc = (int*)(injInst + 1);
                        var tarSrc = (int*)(tarInst + 1);
                        *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
                        *tar = *inj;
#endif
                    }
                }
            }
        }

and replacing not virtual methods

        static void ReplaceInner(MethodInfo methodToReplace, MethodInfo methodToInject)
        {
            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    var inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;
                    var tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2;
#if DEBUG
                    var injInst = (byte*)*inj;
                    var tarInst = (byte*)*tar;
                    var injSrc = (int*)(injInst + 1);
                    var tarSrc = (int*)(tarInst + 1);

                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
                    *tar = *inj;
#endif
                }
                else
                {
                    ulong* inj = (ulong*)methodToInject.MethodHandle.Value.ToPointer() + 1;
                    ulong* tar = (ulong*)methodToReplace.MethodHandle.Value.ToPointer() + 1;
#if DEBUG
                    var injInst = (byte*)*inj;
                    var tarInst = (byte*)*tar;
                    var injSrc = (int*)(injInst + 1);
                    var tarSrc = (int*)(tarInst + 1);

                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
                    *tar = *inj;
#endif
                }
            }
        }
    }
Community
  • 1
  • 1
Евгений
  • 125
  • 1
  • 8
  • Only one question comes to my mind when I read this: Why? – Thorsten Dittmar Aug 19 '16 at 08:46
  • @Thorsten Dittmar reverse engineering and similar techniques? ... Anyway, I'm curious :) and will try soon... –  Aug 19 '16 at 09:18
  • @Thorsten Dittmar, Hard to say no to native language. Please treat with understanding. It is very necessary for the profiling of large software. To quickly place the performance counters. And so that no traces of the following runs. – Евгений Aug 19 '16 at 10:54
  • If the *replacement* class is a UserControl you need to replace `= *inj;` with `= methodToInject.MethodHandle.GetFunctionPointer().ToInt32();` This is sure and tested. Now, when the *target* is a UserControl, the problem is again that the `GetFunctionPointer` returns the correct address, but it's unclear to me how to find its pointer. I guess this is due to the fact that winforms derive from MarshalByRefObject and so I'm not even sure that an injection can be done in managed code... –  Aug 19 '16 at 17:36
  • Does not work with `[assembly: System.Diagnostics.Debuggable(true, true)]` set in AssemblyInfo.cs – Mike de Klerk Apr 14 '17 at 14:16

1 Answers1

4

When the target class is derived from MarshalByRefObject, then the ReplaceInner (for normal methods) stops working but ReplaceVirtualInner (for overridden methods) is ok.

MarshalByRefObject is the base class for objects that communicate across application domain boundaries by exchanging messages using a proxy. Objects that do not inherit from MarshalByRefObject are implicitly marshal by value. When a remote application references a marshal by value object, a copy of the object is passed across application domain boundaries.

This could be partially fixed by marking as virtual the method to replace.

But when the target class is derived from Content also the ReplaceVirtualInner (for overridden methods) stops working.

Unfortunately Windows.Forms are derived from both of them, so I don't see an easy work-around.

Different approaches and alternatives

You may want to consider a different approach: a basic example of tracing with PostSharp and Aspect-Oriented Programming, a CodeProject article and the doc about tracing.

Furthermore, another alternative (don't know if possible for you) is to use the UserControl of WPF instead of Forms and in that case the normal method replacement would work fine (after you've imported the needed assemblies and made the Main [STAThread])

Final solution with Reverse Engineering

Ok, if you really want to make it work at any cost, let's proceed with reversing the target.

Open your compiled .exe with CFF Explorer.

Locate the tables under .Net Directory> MetaData Streams and ungroup the Method Tables. You will find the 2 methods with the same name and different RVA corresponding to the 2 classes (TypeDef). You simply have to ovverride the target RVA with the injection method RVA and save the reversed exe with a new name.

  • I've edited my answer to include a solution by reverse engineering, in case the other alternatives were not immediately viable for you. –  Aug 20 '16 at 07:11