有没有办法改变地址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();
}
答案 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
这表明我已成功修改了从Main
→A
→B
→C
到Main
→A
的调用堆栈→A
→C
。
但是当它在发布模式下运行时,当使用 F5 运行时,或者当我将局部变量添加到B
或C
时,它会停止工作,显示这是多么脆弱。
所以,它可以完成,但你永远不应该这样做(除了教育目的)。