有问题 Dynamically replace the contents of a C# method? 我从@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()");
}
}
}
一切正常,但无法替换覆盖的方法。
最佳答案
更新的答案
首先,请记住
Method Address = Method Virtual Address + base address of class that declares this member..
如果要替换的方法是一个虚拟重写方法,请使用下面的方法。
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
sell)在Release
模式下编译的exe
,而不是在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
原因在this中有解释评论
in debug compiler adds some middle man code and to inject your method you need to recalculate address of your method
问题编辑后
查看修改后的问题,我确认如果将 Base
方法声明为 virtual
就会出现问题。我正在尝试寻找解决方法。
解决方法 1
我的第一个想法是替换 new
keyworkd 而不是 override
(所以当 Base 方法不是 virtual
时)。
这使得它工作,所以我猜想(当我们有一个虚拟方法时)注入(inject)应该发生在基类级别可能......并且不同的行为必须与使用有关callvirt
与 调用
关于c# - 如何在我的方法的指针中替换指向被覆盖(虚拟)方法的指针? (发布 x64 和 x86),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38782934/