4

In question Dynamically replace the contents of a C# method? I found a good response from @ Logman. I do not have standing to ask it in the comments.

using System;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace ReplaceHandles
{
    class Program
    {
        static void Main(string[] args)
        {
            Injection.replace();
            Target target = new Target();
            target.test();
            Console.Read();
        }
    }

    public class Injection
    {
        public static void replace()
        {
            MethodInfo methodToReplace = typeof(Target).GetMethod("test", BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            MethodInfo methodToInject = typeof(Target2).GetMethod("test", BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
            ReplaceInner(methodToReplace, methodToInject);
        }

        static void ReplaceInner(MethodInfo methodToReplace, MethodInfo methodToInject)
        {
            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;
                    int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2;
                    *tar = *inj;
                }
                else
                {
                    ulong* inj = (ulong*)methodToInject.MethodHandle.Value.ToPointer() + 1;
                    ulong* tar = (ulong*)methodToReplace.MethodHandle.Value.ToPointer() + 1;
                    *tar = *inj;
                }
            }
        }
    }


    public class Base
    {
        public virtual void test()
        {

        }
    }

    public class Target : Base
    {
        public override void test()
        {
            Console.WriteLine("Target.test()");
        }

        public void test3()
        {
            Console.WriteLine("Target.test3()");
        }
    }

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

Everything works, but does not work the replacement of overridden methods.

Community
  • 1
  • 1
Евгений
  • 125
  • 1
  • 8
  • ```if (IntPtr.Size == 4) { int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;``` ... do you happen to have any resources as to why it's `+2`? – evandrix Aug 12 '21 at 07:36

1 Answers1

10

Updated Answer

First of all, keep in mind that

Method Address = Method Virtual Address + base address of class that declares this member..

If the method to replace is a virtual overridden method, please use the following.

if (methodToReplace.IsVirtual)
{
    ReplaceVirtualInner(methodToReplace, methodToInject);
} else {
    ReplaceInner(methodToReplace, methodToInject);
}

Tested with both platform target x86 and x64: it works!!!.

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

            uint* inj = (uint*)methodToInject.MethodHandle.Value.ToPointer() + 2;
            //int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2;
            *tar = *inj;
        }
        else
        {
            ulong* classStart = (ulong*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer();
            classStart += 8;
            classStart = (ulong*)*classStart;
            ulong* tar = classStart + index;

            ulong* inj = (ulong*)methodToInject.MethodHandle.Value.ToPointer() + 1;
            //ulong* tar = (ulong*)methodToReplace.MethodHandle.Value.ToPointer() + 1;
            *tar = *inj;
        }
    }
}

Original Answer

You have to run (from a cmd sell) the exe compiled in Release mode, not in Debug.

I've tried and I confirm it does not throw exceptions in that case.

C:\dev\Calc>C:\dev\Calc\bin\Release\Calc.exe
Target.targetMethod1()
Target.targetMethod2()
Not injected 2
Target.targetMethod3(Test)
Target.targetMethod4()

Version x64 Release


Version x64 Release


Version x64 Release


Version x64 Release

Injection.injectionMethod1
Injection.injectionMethod2
Injected 2
Injection.injectionMethod3 Test

as you can see, the above runs whithout the following exception

C:\dev\Calc>C:\dev\Calc\bin\Debug\Calc.exe
Target.targetMethod1()
Target.targetMethod2()
Not injected 2
Target.targetMethod3(Test)
Target.targetMethod4()

Version x64 Debug


Version x64 Debug


Version x64 Debug


Version x64 Debug


Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at InjectionTest.Target.targetMethod1() in C:\dev\Calc\Program.cs:line 38
   at InjectionTest.Target.test() in C:\dev\Calc\Program.cs:line 31
   at InjectionTest.Program.Main(String[] args) in C:\dev\Calc\Program.cs:line 21

and the reason is explained in this comment

in debug compiler adds some middle man code and to inject your method you need to recalculate address of your method

After the question's edit

Looking at the revised question, I confirm that there is an issue if the Base method is declared as virtual. I'm trying to find a workaround.

workaround 1

My first idea was to replace the new keyworkd instead of the override (so when the Base method is not virtual). That makes it work, so I guess that (when we have a virtual method) the injection shoud happen at the base class level maybe... and the different behaviour must have to do with using callvirt vs call

Community
  • 1
  • 1
  • Yes, there are no errors. In the example of the lack overridden method. The code is taken from the original response. In my code, I added a base class "Base" with a virtual method targetMethod1(). In a derived class "Target", I override this method. In this case, it does not work. There are no errors, but does not work – Евгений Aug 05 '16 at 07:50
  • sorry, I'm not not sure to understand: are you running the debug or the release exe and also pls clarify what is your expected result. Thanks a lot. In the meantime I can add a comment pointing here for you –  Aug 05 '16 at 07:54
  • Also, I suggest you should edit your question and show your *full* code. –  Aug 05 '16 at 08:07
  • 1
    Sorry, I had to immediately do so. Example corrected. I work in release. – Евгений Aug 05 '16 at 08:26
  • @Евгений Look at the updated answer, test it yourself and if it's ok pls mark this as **answer**, thanks! )) –  Aug 05 '16 at 17:31
  • Taking a step back, what is it you are trying to do? – brumScouse Aug 05 '16 at 20:26
  • @brumScouse Hi! :-) What I *have* done (I'm not *trying* any longer) is taking a code that was working for normal methods and extend it also to the case of virtual/override methods, i.e. I've answered the question. In particular the program is a method replacer that injects a new function into a target... Hope I've clarified the matter... It was difficult cause this is not documented ;) –  Aug 05 '16 at 20:56
  • @Machine Learning, I have an error "An unhandled exception of type 'System.AccessViolationException' occurred in ReplaceHandles.exe". In this line, * tar = * inj; Why? I ran in the Release x86 and x64. In both cases, such an error. – Евгений Aug 08 '16 at 04:36
  • I'm in configuration Releaase, Platform Target Any Cpu or x86 (I see an exception if I set platform target to x64) –  Aug 08 '16 at 05:02
  • Mystic. The code is identical. Launched in Release Any Cpu - works, then launched x86 - works too, x64 - an error. – Евгений Aug 08 '16 at 05:20
  • ...but so, that's a marginal issue. Basically this answers your question :) either you edit your question again (but I don't think it's so important) or I honestly think this should be marked as answer. Anyway I'm on mobile, I can't add much more... –  Aug 08 '16 at 06:01
  • Response halfway. I am pleased to note your answer, if it is complete. – Евгений Aug 08 '16 at 06:09
  • If this answer works as expected for x86 and the **only** missing thing is to make it work also for x64, please edit your question and specify it: I will complete the answer **later** –  Aug 08 '16 at 06:28
  • I edited the question. I think that by default, the code should work as x64 and x86. – Евгений Aug 08 '16 at 06:40
  • @Евгений it was `classStart += 8;` for x64. now please double check, let me know and finally note this as answer thanks –  Aug 08 '16 at 11:51
  • 1
    @Machine Learning, thank you very much, you really helped me! Sorry I do not have 15 reputation to enhance the reputation of the answer. – Евгений Aug 08 '16 at 12:10
  • Great work you done here. I will try to see if I can add debug mode but for now I don't understand one thing. Is delta you add to classStart (`classStart += 8; //10;`) fixed or can change for some reason? – Logman Aug 08 '16 at 18:07
  • @Logman Thanks, it's very kind of you! I add a fixed delta of 10 for x86 and a delta of 8 instead for x64: it's not well documented *afaik*, I found the latter *experimentally* :) –  Aug 08 '16 at 19:25
  • Hi @Machine Learning! Maybe look at my problem. http://stackoverflow.com/questions/39034018/how-to-replace-a-pointer-to-a-pointer-to-a-method-in-a-class-of-my-method-inheri – Евгений Aug 19 '16 at 08:05