在 STFLD 导致 InvalidProgramException 之前添加对方法的调用

Adding a call to a method before a STFLD causes an InvalidProgramException

我可以访问这样的函数体中间语言:

byte[] ilCodes = NestedFooInfo.GetMethodBody().GetILAsByteArray();

我希望能够修改其 IL 代码,以便每当有 stfld IL 命令时,我都会调用以下名为 OnChangeField:

的方法
public static void OnChangeField(object obj, object value)
{
    Console.WriteLine("VICTORY");
    return;
}

到目前为止我是这样做的:

我定义了我要调用的方法的调用指令:

MethodInfo OnStfld = typeof(MethodBoundaryAspect).GetMethod("OnChangeField");
byte[] callIL = new byte[5];
callIL[0] = (byte)OpCodes.Call.Value;
callIL[1] = (byte)(OnStfld.MetadataToken & 0xFF);
callIL[2] = (byte)(OnStfld.MetadataToken >> 8 & 0xFF);
callIL[3] = (byte)(OnStfld.MetadataToken >> 16 & 0xFF);
callIL[4] = (byte)(OnStfld.MetadataToken >> 24 & 0xFF);

然后我像这样修改原始的(NestedFoo(...))方法主体代码:

byte[] ilCodes = NestedFooInfo.GetMethodBody().GetILAsByteArray();
var stfldOpCode = (byte)OpCodes.Stfld.Value;
for (int i = 0; i < ilCodes.Length; i++)
{
    if (ilCodes[i] == stfldOpCode)
    {
         byte[] newIlCodes = ilCodes.Take(i).Concat(callIL).Concat(ilCodes.Skip(i)).ToArray(); // Insert the call instruction before the s
         InjectionHelper.UpdateILCodes(NestedFooInfo, newIlCodes); // Explanation below
         break; // Currently I just want to hook the first stfld as a PoC
     }
}

我的测试用例中修改的方法体是这样的:

public class ExceptionHandlingService : IExceptionHandlingService
{
        public static string var1 = "initialValue";
        public static string Var2 { get; set; } = "initialValue";
        public string var3 = "initialValue";
        public string Var4 { get; set; } = "initialValue";

        public string NestedFoo(SampleClass bar)
        {
            var1 = "value set in NestedFoo()";
            Var2 = "value set in NestedFoo()";
            var3 = "value set in NestedFoo()";
            Var4 = "value set in NestedFoo()";
            AddPerson("From", "NestedFoo", 2);
            return Foo();
        }
        [...]
}

我这样调用方法:

var a = new ExceptionHandlingService();
var b = new SampleClass("bonjour", 2, 3L); // Not really relevant
a.NestedFoo(b);

我得到一个:

System.InvalidProgramException: 'Common Language Runtime detected an invalid program.'

或一个

System.BadImageFormatException: 'Index not found. (Exception from HRESULT: 0x80131124)'

如果我从 ExceptionHandlingService

中删除 postsharp 和服务接口

我想我以导致无效代码流的方式编辑了 Il 代码,但查看 callstfld 文档 here(p368 和 p453)我不知道不知道我做错了什么。

对于那些想知道魔法发生了什么的人 InjectionHelper.UpdateILCodes(NestedFooInfo, newIlCodes); 您可以查看 this link,其中显示了如何在运行时编辑 Il 代码。

以下异常是由执行程序集外部的 OnChangeField() 方法的元数据标记引起的。

System.BadImageFormatException: 'Index not found. (Exception from HRESULT: 0x80131124)'

下面的异常是由应用到 NestedFoo 函数的 postsharp 方面引起的,而且还因为我没有将任何参数压入堆栈以传递给 OnchangeField 参数:

System.InvalidProgramException: 'Common Language Runtime detected an invalid program.'

IL 有点脆弱,它并不是真的要手工组装 :) 无论如何,您无法仅通过查找单个字节值来找到 stfld - 您需要确保您实际上是在读取操作码,而不是参数等。

如果您没有绝对的理由不这样做,我建议您使用 Expression 树来编译“原始”IL - 这应该会让您省去很多麻烦。无论如何,尝试以非常小的步骤移动 - 例如首先尝试在方法的开头注入一个调用,尝试一个没有参数的方法,然后尝试找到 stfld,处理更复杂的情况(装箱等)...

要前进,使用编译器和反编译器会有所帮助。编译调用您的方法的普通 C# 代码,并将其反编译为 IL。尝试注入,并反编译生成的完整 IL 字节数组——您可能会发现错误(尽管我不会说“足够简单”,因为它需要一点 IL 经验才能发现大多数错误 :))。

当场跳出来的一件事是您保留了 stfldcall,但只传递了一次参数。同样,作为确定问题的一部分,它有助于一次做一件事情——将 stfld 替换为 call 以确保该部分有效;然后处理 dup 堆栈上的值以允许您调用这两个操作。