Как заменить указатель на переопределенный (виртуальный) метод в указателе моего метода? (Выпуск x64 и x86)

В вопросе Динамически заменить содержимое метода C#? Я нашел хороший ответ от @ Logman. Я не имею права задавать это в комментариях.

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()");
        }
    }
}

Все работает, но не работает замена переопределенных методов.

1 ответ

Решение

Обновленный ответ

Прежде всего, имейте в виду, что

Адрес метода = Виртуальный адрес метода + базовый адрес класса, который объявляет этого члена.

Если метод для замены является виртуальным переопределенным методом, используйте следующее.

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

Протестировано с целевой платформой x86 и x64: это работает!!!.

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;
        }
    }
}

Оригинальный ответ

Вы должны бежать (от cmd продать) exe составлено в Release режим, не в Debug,

Я пытался, и я подтверждаю, что это не вызывает исключений в этом случае.

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

как видите, вышесказанное работает без следующего исключения

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

и причина объясняется в этом комментарии

в отладчик компилятор добавляет некоторый код среднего человека и для внедрения вашего метода вам необходимо пересчитать адрес вашего метода

После редактирования вопроса

Глядя на пересмотренный вопрос, я подтверждаю, что есть проблема, если Base метод объявлен как virtual, Я пытаюсь найти обходной путь.

Обходной путь 1

Моей первой идеей было заменить new ключом вместо override (поэтому, когда базовый метод не virtual). Это заставляет его работать, поэтому я предполагаю, что (когда у нас есть виртуальный метод) инъекция должна произойти на уровне базового класса, возможно... и другое поведение должно быть связано с использованием callvirt против call

Другие вопросы по тегам