【问题标题】:Adding a call to a method before a STFLD causes an InvalidProgramException在 STFLD 之前添加对方法的调用会导致 InvalidProgramException
【发布时间】:2021-02-25 10:18:35
【问题描述】:

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

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 检测到无效程序。'

或者一个

System.BadImageFormatException: '未找到索引。 (HRESULT 异常:0x80131124)'

如果我从 ExceptionHandlingService 中删除 postsharp 和服务接口

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

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

【问题讨论】:

  • 您能否展示一个示例输入 IL 流示例以及您希望最终 IL 在注射后的样子?我之所以这么问,是因为还不清楚最终的 IL 会是什么样子。特别是我想知道您从哪里获得对OnChangeField 的调用的参数值。

标签: c# jit cil


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

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

    System.BadImageFormatException: '未找到索引。 (例外来自 HRESULT: 0x80131124)'

    以下异常是由应用于 NestedFoo 函数的 postsharp 方面引起的,但也因为我没有将堆栈上的任何参数推送到要传递给 OnchangeField 参数:

    System.InvalidProgramException: 'Common Language Runtime 检测到无效程序。'

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-27
      • 1970-01-01
      • 2015-08-15
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多