如何使用 Mono Cecil 更改字段值?

问题描述

我有一个 C# 程序,它有一个类:

public class Test
{
    internal const string a = "some value";
    private DateTime b = new DateTime();
}

如何使用 Mono Cecil 更改它们的初始值,使其看起来像这样:

public class Test
{
    internal const string a = "test";
    private DateTime b = DateTime.MaxValue;
}

现在我只有以下骨架代码,我不知道如何修改字段。

void Main()
{
    var input = @"C:\my_projects\a.exe";
    var asm = AssemblyDeFinition.ReadAssembly(input);
    foreach (ModuleDeFinition module in asm.Modules)
    {
        foreach (TypeDeFinition type in module.GetTypes())
        {

            foreach (var field in type.Fields)
            {
                if (field.Name == "a")
                {

                }

                else if (field.Name == "b")
                {

                }

            }
        }
    }
    asm.Write(@"c:\my_projects\b.exe");
}

解决方法

免责声明

前面的代码非常脆弱

对于常量,它是设置 fld.Constant 属性的问题。

对于实例/静态字段,C# 编译器将在构造函数中发出初始化代码,因此您需要找到加载将存储在该字段中的值并替换它的指令。

using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace x
{
    class Program
    {
        public const string ConstValue = "old";
        public int instanceField = 1;

        static void Main(string[] args)
        {
            var o = new Program();
            if (o.instanceField == 1)
            {
                using var a = AssemblyDefinition.ReadAssembly(typeof(Program).Assembly.Location);
                var t = a.MainModule.Types.Single(ct => ct.Name == "Program");

                var constant = t.Fields.First(f => f.Name == "ConstValue");
                constant.Constant = "new value";

                var ctor = t.Methods.Single(m => m.IsConstructor);
                System.Console.WriteLine(ctor);
                var il = ctor.Body.GetILProcessor();

                var inst = il.Body.Instructions.First();
                while (inst != null)
                {
                    System.Console.WriteLine($"Checking {inst}");
                    if (inst.OpCode == OpCodes.Stfld && ((FieldReference) inst.Operand).Name == "instanceField")
                    {
                        // notice that this is very fragile. For this specific
                        // case I'd say it is safe but depending on how you
                        // initialize the field the instruction that loads 
                        // the value to be assigned to the field may be located 
                        // several instructions prior to the actual assignment
                        // but you can keep track of the stack state and figure 
                        // out which instruction is pushing the value that
                        // will end up being poped into the field.
                        
                        il.Replace(inst.Previous,il.Create(OpCodes.Ldc_I4,42));                        
                        System.Console.WriteLine("Found");
                        break;
                    }
                    inst = inst.Next;
                }

                var  p = typeof(Program).Assembly.Location;
                var newBinaryPath =  Path.Combine(Path.GetDirectoryName(p),Path.GetFileNameWithoutExtension(p) + "2" +Path.GetExtension(p));
                a.Write(newBinaryPath);
                System.Console.WriteLine($"New binary writen to {newBinaryPath}");
            }

            System.Console.WriteLine(o.instanceField);

            // Notice that no matter what you do,this will always print the 
            // old value; that happens because the C# compiler will emit the
            // constant at the call site (instead of referencing the field
            // at runtime)
            System.Console.WriteLine(ConstValue);
        }
    }
}