1. 反编译
获取程序集方法的 IL 代码。
代码原形:
public class Class1 { public void Test() { Console.WriteLine("Hello, World!"); } }
Lutz Roeder's Reflector 反编译结果:
.method public hidebysig instance void Test() cil managed { .maxstack 8 L_0000: nop L_0001: ldstr "Hello, World!" L_0006: call void [mscorlib]System.Console::WriteLine(string) L_000b: nop L_000c: ret }
使用 Mono Cecil 进行反编译:
namespace ConsoleApp { using System; using System.Text; using Mono.Cecil; using Mono.Cecil.Cil; class Program { static void Main(string[] args) { AssemblyDefinition asm = AssemblyDefinition.ReadAssembly("MyLibrary.dll"); foreach (TypeDefinition type in asm.MainModule.Types) { if (type.Name.Equals("Class1")) { foreach (MethodDefinition method in type.Methods) { Console.WriteLine("Method Name: "+method.Name); Console.WriteLine(".maxstack {0}", method.Body.MaxStackSize); foreach (Instruction ins in method.Body.Instructions) { Console.WriteLine("L_{0}: {1} {2}", ins.Offset.ToString("X4"), ins.OpCode.Name, ins.Operand is string ? string.Format("\"{0}\"", ins.Operand) : ins.Operand); }//end for Instruction }//end for MethodDefinition } }//end for TypeDefinition Console.ReadLine(); } } }
输出:
2. 修改 IL 代码
继续以上面的代码为例,我们的目标是将 "Hello, World!" 改成 "Hello, C#!"。当然,更复杂也可以,只不过需要你自己花时间了。
AssemblyDefinition asm = AssemblyDefinition.ReadAssembly("MyLibrary.dll"); foreach (TypeDefinition type in asm.MainModule.Types) { if (type.Name.Equals("Class1")) { foreach (MethodDefinition method in type.Methods) { if (method.Name == "Test") { foreach (Instruction ins in method.Body.Instructions) { if (ins.OpCode.Name.Equals("ldstr") && ins.Operand.ToString().StartsWith("Hello")) { ins.Operand = "Hello,C#"; } } } }//end for MethodDefinition } }//end for TypeDefinition asm.Write("MyLibrary.dll");
用 Lutz Roeder's Reflector 打开 Test.dll 看看结果。
.method public hidebysig instance void Test() cil managed
{
.maxstack 8
L_0000: nop
L_0001: ldstr "Hello, C#!"
L_0006: call void [mscorlib]System.Console::WriteLine(string)
L_000b: nop
L_000c: ret
}
达成所愿~~~~~
3. 代码注入
完成代码修改以后,我们玩一个更难点的,注入额外的代码。(好像有点做病毒的意思,呵呵~)
任务目标是在 Console.WriteLine("Hello, World!"); 前注入 Console.WriteLine("Virus? No!");,还好这不是真的破坏性代码。
AssemblyDefinition asm = AssemblyFactory.GetAssembly("MyLibrary.dll");
foreach (TypeDefinition type in asm.MainModule.Types)
{
if (type.Name == "Class1")
{
foreach (MethodDefinition method in type.Methods)
{
if (method.Name == "Test")
{
foreach (Instruction ins in method.Body.Instructions)
{
if (ins.OpCode.Name == "ldstr" && (string)ins.Operand == "Hello, World!")
{
CilWorker worker = method.Body.CilWorker;
Instruction insStr = worker.Create(OpCodes.Ldstr, "Virus? NO!");
worker.InsertBefore(ins, insStr);
MethodReference refernce = asm.MainModule.Import(typeof(Console).GetMethod("WriteLine",
new Type[]{typeof(string)}));
Instruction insCall = worker.Create(OpCodes.Call, refernce);
worker.InsertAfter(insStr, insCall);
Instruction insNop = worker.Create(OpCodes.Nop);
worker.InsertAfter(insCall, insNop);
break;
}
}
}
}
}
}
继续用 Lutz Roeder's Reflector 看一下结果。
C#
public void Test()
{
Console.WriteLine("Virus? NO!");
Console.WriteLine("Hello, World!");
}
IL
.method public hidebysig instance void Test() cil managed
{
.maxstack 8
L_0000: nop
L_0001: ldstr "Virus? NO!"
L_0006: call void [mscorlib]System.Console::WriteLine(string)
L_000b: nop
L_000c: ldstr "Hello, World!"
L_0011: call void [mscorlib]System.Console::WriteLine(string)
L_0016: nop
L_0017: ret
}
用反射测试一下修改后的程序集。
AssemblyFactory.SaveAssembly(asm, "Test.dll");
Assembly testAssembly = Assembly.LoadFrom("Test.dll");
Type class1Type = testAssembly.GetType("MyLibrary.Class1");
Object o = Activator.CreateInstance(class1Type);
class1Type.InvokeMember("Test", BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod, null, o, null);
输出:
Virus? No!
Hello, World!
一切OK! 注入更复杂的代码,甚至是为类型添加方法,或者直接用来做 AOP…… 好像还有很多事可做……
4. 获取程序集信息
AssemblyDefinition asm = AssemblyFactory.GetAssembly("Learn.Library.dll");
Console.WriteLine("Kind:{0}", asm.Kind);
Console.WriteLine("Runtime:{0}", asm.Runtime);
输出:
Kind:Dll
Runtime:NET_2_0
利用 ModuleDefinition.Image 属性,我们可以获取程序集几乎全部的细节信息。包括 CLIHeader、DebugHeader、DOSHeader、FileInformation、HintNameTable、 ImportAddressTable、ImportLookupTable、ImportTable、MetadataRoot、 PEFileHeader、PEOptionalHeader、ResourceDirectoryRoot、Sections、TextSection 等。
------------
继续关注和研究,也欢迎大家一起交流。
有一个名为 MyLibrary.dll 的程序集,内有 Class1 类型,我们需要动态创建其对象,并调用 Test 方法。注入的要求是在执行 Test 内部代码前先执行一个外部程序集的方法 InjectMethod。
class Class1
{
void Test()
{
Console.WriteLine("Hello, World!");
}
}
注入代码
public class Program
{
public static void InjectMethod()
{
Console.WriteLine("Haha...");
}
static void Main(string[] args)
{
AssemblyDefinition asm = AssemblyFactory.GetAssembly("MyLibrary.dll");
foreach (TypeDefinition type in asm.MainModule.Types)
{
if (type.Name == "Class1")
{
foreach (MethodDefinition method in type.Methods)
{
if (method.Name == "Test")
{
Instruction ins = method.Body.Instructions[0];
CilWorker worker = method.Body.CilWorker;
MethodInfo m = typeof(Program).GetMethod("InjectMethod", BindingFlags.Static | BindingFlags.Public);
MethodReference refernce = asm.MainModule.Import(m);
Instruction insCall = worker.Create(OpCodes.Call, refernce);
worker.InsertBefore(ins, insCall);
Instruction insNop = worker.Create(OpCodes.Nop);
worker.InsertAfter(insCall, insNop);
}
}
}
}
Assembly testAssembly = AssemblyFactory.CreateReflectionAssembly(asm);
Type class1Type = testAssembly.GetType("MyLibrary.Class1");
Object o = Activator.CreateInstance(class1Type);
class1Type.InvokeMember("Test", BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod, null, o, null);
}
}
输出:
Haha...
Hello, World!
动态注入和静态注入不同,它不会修改目标程序集文件,因此不会有强类型签名等问题,也不易发生相关 "版权" 问题。可用来跳过某段验证代码,或者替换某些内部执行代码,用途有点像 "外挂"。使用动态注入实现 AOP 可能比较有意思,我们可以遍历添加了 "[AOP(aopMethodName=)]" 方法特性的所有类型,然后动态注入 AOP 拦截代码。
-----------------------
除了 Mono.Cecil,还有另外一个选择 PostSharp。
http://www.postsharp.org
About PostSharp
With PostSharp, you can develop custom attributes that change your code. And you can do more!
PostSharp is a post-compiler: an open platform for analysis and transformations of .net assemblies after compilation.
ASPect-Oriented Programming (AOP) or Policy Injection are two of the major applications, but only two of them.
PostSharp Laos is a high-level aspect weaver that makes aspect-oriented programming (AOP) extremely easy. But Laos is only an illustration of the complete Platform. PostSharp is used to perform low-level MSIL injection. It serves as a base for persistence layers, optimizers or custom AOP weavers.
上午和同事讨论实现 AOP 时,曾聊到一个问题,如何自动执行初始化操作,从而实现使用 MSIL Injection 或者 Emit 进行方法拦截。由于目标类型可能存放于一个类库中,除非强制要求调用人员执行一个初始化代码,否则初始化注入就只能是摆设。网上常见的动态代理方式,要求用户必须使用代理工厂来创建目标类型,而我们聊到的方案中并不打算这么做。今天在博客园看到一篇文章 《程序集的初始化及合并》给了我点启发。当用户调用类库的时候,必然会先载入 "<Module>" 类型,那么其静态构造必然被执行,因此在 "<Module>" 静态构造中静态注入一些代码,即可实现全局的初始化操作。本文无意讨论 AOP 实现,重点是如何实现类库自动初始化操作。和原文作者使用 ILMerge 不同的是,我将使用 Mono Cecil 创建 "<Module>" 的静态构造方法。
using Mono.Cecil;
using Mono.Cecil.Cil;
...
AssemblyDefinition asm = AssemblyFactory.GetAssembly("MyLibrary.dll");
foreach (TypeDefinition type in asm.MainModule.Types)
{
if (type.Name == "<Module>")
{
MethodAttributes attr = MethodAttributes.Private | MethodAttributes.HideBySig |
MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static;
// 创建静态构造方法
AssemblyNameReference asmNameRef = new AssemblyNameReference("mscorlib", "",
new Version("2.0.0.0"));
TypeReference ret = new TypeReference("Void", "System", asmNameRef, false);
MethodDefinition cctor = new MethodDefinition(".cctor", attr, ret);
type.Constructors.Add(cctor);
// 实现静态构造方法体
cctor.Body.MaxStack = 8;
CilWorker worker = cctor.Body.CilWorker;
worker.Append(worker.Create(OpCodes.Nop));
worker.Append(worker.Create(OpCodes.Ldstr, "moudle init..."));
worker.Append(worker.Create(OpCodes.Call,
asm.MainModule.Import(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }))));
worker.Append(worker.Create(OpCodes.Nop));
worker.Append(worker.Create(OpCodes.Ret));
break;
}
}
// 保存修改后的程序集
AssemblyFactory.SaveAssembly(asm, "MyLibrary2.dll");
// 随便类库中某个类型,查看初始化是否执行。
System.Reflection.Assembly exeasm = AssemblyFactory.CreateReflectionAssembly(asm);
Type exetype = exeasm.GetType("MyLibrary.Class1");
object o = Activator.CreateInstance(exetype);
输出:
moudle init...
Class1 static...
Class1 Constructor...
使用 Lutz Roeder's Reflector 查看 MyLibrary2.dll 的 IL 代码。
.class private auto ansi <Module>
{
.method private hidebysig specialname rtspecialname static void .cctor() cil managed
{
.maxstack 8
L_0000: nop
L_0001: ldstr "moudle init..."
L_0006: call void [mscorlib]System.Console::WriteLine(string)
L_000b: nop
L_000c: ret
}
}
1. 添加程序集引用
AssemblyDefinition asm = AssemblyFactory.GetAssembly("MyLibrary.dll");
// 应该是程序集完整信息,包括名称、语言以及版本号。
AssemblyNameReference cecil = new AssemblyNameReference("Mono.Cecil", "", new Version("0.5.0.0"));
asm.MainModule.AssemblyReferences.Add(cecil);
foreach (AssemblyNameReference r in asm.MainModule.AssemblyReferences)
{
Console.WriteLine(r.FullName);
}
AssemblyFactory.SaveAssembly(asm, "MyLibrary2.dll");
2. 添加类型
在程序集中创建新的类型及其成员。
AssemblyDefinition asm = AssemblyFactory.GetAssembly("MyLibrary.dll");
AssemblyNameReference asmRef = new AssemblyNameReference("mscorlib", "", new Version("2.0.0.0"));
TypeReference baseType = new TypeReference("Object", "System", asmRef, false);
TypeReference returnType = new TypeReference("Void", "System", asmRef, false);
TypeReference intType = new TypeReference("Int32", "System", asmRef, false);
// 创建类型
TypeAttributes myClassAttribute = TypeAttributes.Class | TypeAttributes.Public;
TypeDefinition myClass = new TypeDefinition("Class1", "MyNamespace", myClassAttribute, baseType);
asm.MainModule.Types.Add(myClass);
// 添加默认构造方法
MethodAttributes ctorAttribute = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;
MethodDefinition ctor = new MethodDefinition(".ctor", ctorAttribute, returnType);
myClass.Constructors.Add(ctor);
// 创建默认构造方法体
ctor.Body.MaxStack = 8;
CilWorker worker = ctor.Body.CilWorker;
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, asm.MainModule.Import(typeof(Object).GetConstructor(new Type[0]))));
worker.Append(worker.Create(OpCodes.Nop));
worker.Append(worker.Create(OpCodes.Ret));
// 添加字段
FieldDefinition field = new FieldDefinition("i", intType, FieldAttributes.Private);
myClass.Fields.Add(field);
// ...
AssemblyFactory.SaveAssembly(asm, "MyLibrary2.dll");
还可以使用 PropertyDefinition、EventDefinition、MethodDefinition 添加更多成员,其使用方法比较简单,和我以前写 Emit 文章比较类似,本文不再详述。