Как заменить указатель на переопределенный (виртуальный) метод в указателе моего метода? (Выпуск 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