有什么办法可以改变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();
}
最佳答案
嗯,OpCodes.Ret
IL 指令实际上并没有进行任何跳转。相反,IL 代码由 CLR 编译为 native 机器代码并执行。某些 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 在 Debug模式下运行此,它会打印:
A before
B before
A after
A after
这表明我已经成功修改了从 Main
→ A
→ B
→ C
的调用堆栈到 Main
→ A
→ A
→ C
。
但是当在 Release模式下运行时,当使用 F5 运行时或当我将局部变量添加到 B
或 C
时,它会停止工作,可见这是多么脆弱。
所以,这是可以做到的,但是你永远不应该这样做(除非出于教育目的)。
关于c# - OpCodes.Ret 使用的地址存储在哪里?可以改变吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25778356/