c# - 如何在我的方法的指针中替换指向被覆盖(虚拟)方法的指针? (发布 x64 和 x86)

标签 c#

有问题 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);
}

两个 平台目标 x86x64 上进行了测试:它有效!!!。

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/

相关文章:

javascript - 使用更改的 CSS 光标属性在不移动鼠标的情况下更新鼠标光标

c# - 正则表达式在 .NET 中不起作用

c# - 如何根据 C# 中的名称比较两个文件夹中的相似文件?

c# - Filter Type.GetProperties(),其中 PropertyType.Name 位于列表中

c# - TimeSpan 自定义格式抛出异常?

c# - 命名空间 'Cecil' 中不存在类型或命名空间名称 'Mono'

c# - ASP.NET MVC 4 应用程序中的自定义身份验证

c# - Linq 将 2 个列表合并为一个

c# - 两个日期之间的 Lambda 显式

c# - XmlDocument:空白处理和规范化