最近帮朋友做个东西,由于历史原因,有一些文件没有代码,但是又需要修改。这就需要干吃力不讨好的事情了——反编译。Google了一圈,发现.NET平台比较流行的也就是mono项目里的神器——Cecil了。原没Java平台的Javassist用起来里面爽,这里是一个应用。最不爽的是Cecil官方基本没什么文档,只有一个非常简单的介绍和示例。网上相关的文章也都是基于0.6版本的,不幸中大幸就是官方出了文档说明了0.6和0.9之间的差别了。Cecil非常让人不爽的一点是不能直接插入或修改C#代码,只能插入和修改IL代码,悲摧的。
reflector之前有个非常强悍的插件叫Reflexil,也是基于Cecil实现的,可以非常方便地删除,重命名,注入方法的,可惜和新版的reflector不兼容了。再说reflector开始收费了,之前的免费版本也不能用了。这时SharpDevelop的ILSpy就应运而生了,可惜刚出来没多久,相关的插件很少。好吧,其实只有两个,一个还不能用,另一个也没试。废话不多说了,下面进入正题。
由于注入方法需要通过IL代码来实现。下面这个方法可以把执行过程打印出来,帮助进行下面的操作。
123456789101112131415161718192021 | public static void PrintTypes(string fileName){ var asm = AssemblyDefinition.ReadAssembly(fileName); foreach (TypeDefinition type in asm.MainModule.Types) { if (type.Name.Equals("<Module>")) continue; Console.WriteLine("ClassName is : {0}", type.Name); foreach (MethodDefinition method in type.Methods) { Console.WriteLine("{0} .maxstack {1}", method.FullName, method.Body.MaxStackSize); foreach (var 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); } } Console.WriteLine("============================"); }} |
新建一个工程,生成TestCecil.dll
供测试
12345678910111213141516171819 | using System; namespace TestCecil{ public class Class1 { public void Test() { string t = "test_xxxx"; t = t.Replace("_", "."); Console.WriteLine(t); } public void Test1(string content) { Console.WriteLine(content); } }} |
在已有方法中注入新的操作的过程
12345 | var processor = method.Body.GetILProcessor();var newInstruction = processor.Create(OpCodes.Call, someMethodReference);var firstInstruction = method.Body.Instructions[0]; processor.InsertBefore(firstInstruction, newInstruction); |
具体示例,在类TestCecil.Class1
的Test
方法中的第一行,打印字符I am a test message !!!
12345678910111213141516 | static void Main(string[] args){ var asm = AssemblyDefinition.ReadAssembly("TestCecil.dll"); var type = asm.MainModule.GetType("TestCecil.Class1"); var method = type.Methods[0]; var processor = method.Body.GetILProcessor(); var ldstr = processor.Create(OpCodes.Ldstr, "I am a test message !!!"); var call = processor.Create(OpCodes.Call, method.Module.Import( typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }))); processor.InsertBefore(method.Body.Instructions[0], ldstr); processor.InsertAfter(method.Body.Instructions[0], call); asm.Write("TestCecil.patch.dll"); PrintTypes("TestCecil.patch.dll");} |
在类TestCecil.Class1
中注入Test2(string)
方法
1234567891011 | static void Main(string[] args){ var asm = AssemblyDefinition.ReadAssembly("TestCecil.dll"); var module = asm.Modules[0]; var type = module.Types.FirstOrDefault(m => m.Name == "Class1"); var method = new MethodDefinition("Test2", MethodAttributes.Public,module.Import(typeof(void))); type.Methods.Add(method); method.Parameters.Add(new ParameterDefinition(module.Import(typeof(string)))); asm.Write("TestCecil.patch.dll"); PrintTypes("TestCecil.patch.dll");} |
移除一个现有方法
12345678 | public static void RemoveMethod(TypeDefinition self, string methodName){ var method = self.Methods.FirstOrDefault(m => m.Name == methodName); if (method != null) { self.Methods.Remove(method); }} |
具体的使用方法,移除类TestCecil.Class1
中的Test
方法
12345678 | static void Main(string[] args){ var asm = AssemblyDefinition.ReadAssembly("TestCecil.dll"); TypeDefinition type = asm.MainModule.GetType("TestCecil.Class1"); RemoveMethod(type, "Test"); asm.Write("TestCecil.patch.dll"); PrintTypes("TestCecil.patch.dll");} |
这里只是一些简单的示例,常见使用都包括了。不过这种操作只能用来救急用的,偶尔干干还可以,要是在生产环境中用此方法会死人的-_-||
--EOF--