OpCodes.Ret使用存储的地址在哪里?可以改变吗?

时间:2014-09-11 03:01:00

标签: c# cil reflection.emit

有没有办法改变地址OpCodes.Ret跳转到? IL中的方法可以更改C#使用的调用堆栈吗?

据我所知,在C ++中你可以只访问堆栈上的值并更改返回地址等等。在IL中我尝试访问超出当前方法的堆栈的所有内容都失败,并出现异常" InvalidProgramException"。

我的示例代码:

    public static void Test2()
    {
        var ma = typeof(Program).GetMethod("Test2");
        DynamicMethod meth = new DynamicMethod("", null, null, ma.Module, true);
        ILGenerator il = meth.GetILGenerator();



        il.EmitWriteLine("Start to run the IL method");

        var t = il.DeclareLocal(typeof(int));



        //take the top of the stack (from the caller method) and put it into the variable t
        //this seems to be not possible?
        il.Emit(OpCodes.Stloc, t);

        //print the value from the stack
        il.EmitWriteLine(t);

        //load the value back from the field onto the stack to allow everything to preceed normally
        il.Emit(OpCodes.Ldloc, t);

        //return
        il.Emit(OpCodes.Ret);

        Action a = (Action)meth.CreateDelegate(typeof(Action));

        a();
    }

1 个答案:

答案 0 :(得分:2)

嗯,OpCodes.Ret IL指令实际上并没有跳跃。相反,IL代码由CLR编译为本机机器代码,执行。为某些IL代码生成什么机器代码取决于您的体系结构(x86,ARM,...),它是CLR的实现细节。

有理由假设在x86上,ret IL指令被编译为ret x86指令,但可能不是,例如因为整个方法可能是内联的。

要做到这一点,你可以尝试使用指针修改堆栈,因为这是存储地址x86 ret跳转到的地方,但这样做非常危险(你可以很容易地修改错误的内存)而且非常脆弱(因为它依赖于堆栈的布局,这很容易改变)。

例如,请查看以下代码:

using System;

static class Program
{
    static void Main()
    {
        A();
    }

    static void A()
    {
        Console.WriteLine("A before");
        B();
        Console.WriteLine("A after");
    }

    static void B()
    {
        Console.WriteLine("B before");
        C();
        Console.WriteLine("B after");
    }

    static unsafe void C()
    {
        int local;

        int* localPointer = &local;

        localPointer[2] = localPointer[4];
    }
}

如果我在计算机上运行此,在调试模式下使用 Ctrl + F5 ,则会打印:

A before
B before
A after
A after

这表明我已成功修改了从MainABCMainA的调用堆栈→AC

但是当它在发布模式下运行时,当使用 F5 运行时,或者当我将局部变量添加到BC时,它会停止工作,显示这是多么脆弱。

所以,它可以完成,但你永远不应该这样做(除了教育目的)。