首页 > 代码库 > Dump内存中的Assembly到磁盘
Dump内存中的Assembly到磁盘
发现ConfuserEx这个开源的.NET加密程序用的人也非常多,尤其是老外。屡屡遇到这东西,很头疼,主要是是没有现成的脱壳程序,需要手动脱壳,虽说难度不大,但它更新的速度挺快,一直在变化,总是给人一种追着才能赶上的感觉,真是不爽。话说来,不知国内是真没有大牛,还是大牛都藏着掖着,那些脱壳的程序都是老外的作品,身在天朝又访问不了GG,只能自己动手了。其实本文的目的是根据内存中的源程序集Rebuild一个新的程序集,之所以不是Dump是因为还要把被混淆过的名称和字符串信息还原,显然Dump不能满足这样的要求。
这个事情拖拖拉拉了很久,主要是前期要准备的内容太多,PE和NET头的结构还好说,那45个元数据表就惨了,一是找不着文档,二是我这小学的英语水平找到也看不懂,基于种种原因一直搁浅,直到有一天……无聊下载个ConfuserEx的源代码看看,发现它使用了和de4dot相同的dnlib库,眼前一亮,发现这个库封装了.NET程序集所有元数据的物理结构和基本逻辑,这不就是我寻求已久的真爱吗?好了,放弃我那删改N久的烂尾工程,开始新的旅程吧。
下面进程正题。
通过dnlib这个库,可以获取整个Assembly中的元数据信息,程序集、资源、模块、类型、字段、属性、事件、方法、参数,泛型等等所有内容。使用这个库可以动态的创建程序集,与.NET中System.Reflection.Emit命名空间中创建的程序集有两个明显的区别,一是前者创建的程序集不能直接在内存中运行,必须保存后再加载才可运行,而后者则直接创建在CLR中并可以保存到磁盘;另一个区别是dblib中很强调Signaure,而在日常的.NET编程中很难接触到“签名”这种东西,那么,弄清楚元数据表中的数据与签名的对应的关系则成为是否能Dump一个程序集的重点。
Dump一个内存中Assemlyb的另一个重点则是梳理元数据与元数据的对应关系,根据源程序集中的内容依次添加至新建程序集中,这个过程有点复杂,复杂的主要原因是我对元数据的物理结构不甚了解,以至于走了不少弯路,下面则是我的实现过程。代码不贴,本人为人指路,但不带人走路。
0、创建缓存字典,包括类型、字段、属性、事件、方法、方法参数、泛型参数、方法体参数。这里要说明一下,方法体参数与方法参数并非一一对应关系,在大部分混淆软件中方法参数都会被去掉,因为它并不是一定要有的,唯一的用途就是记录参数的名称;泛型参数包括了泛型方法参数与泛型类型参数,而泛型签名是需要花些时间才能弄明白的。
1、创建程序集与模块,其中包括程序集名称、程序集版本、程序集公钥、模块版本、模块CLR版本、模块目标平台等。
2、创建类型。创建相同命名空间与名称的空类型并缓存对应关系,注意还要循环把Nested类型创建出来。
3、循环上一步所有创建的类型,添加类型的基类、添加类型所使用的接口类型、添加字段、添加属性、添加事件、添加空方法。属性与事件各自有两个方法,分别是set和get,add和remove,在名称上需要还原。更重的重点则是签名,这些类成员的签名都需要重新生成,我提前就写好了一个名为CloneSig的函数,对应CorHdr.h中的每个基础CorElementType,而事实上字段属性方法是组合签名,则根据dnlib库中的各个签名构造函数一一生成。
4、继续上一节,在创建方法时要注意属性与事件的四个固定名称的方法不要重要创建,还要注意方法的泛型参数与泛型签名,在大部分混淆过的程序集中,泛型签名与泛型参数还有方法体参数与方法参数都不是一一对应的关系,在这里我们要予以重建并恢复名称。此外还有方法参数与字段的常数值不能忘记。
5、创建方法间的对应关系。对应的就是new、virtual与override。重点是需要所有方法都创建以后,再从缓存中找出对应的关系重塑方法间的关系。这里我有一个名为CloneMethod的函数,主要是区分模块内定义的方法与从其它模块引用的方法,如果是引用的方法则调用另一个函数CloneMember(下文介绍)
6、重中之重,方法体的重建。这里并不需要关心方法是TINY或是BIG,dnlib会帮我们处理。
6.1,按着方法体的局部变量签名,重新创建局部变量,并缓存之。
6.2,遍历每一条IL指令,然后重新生成新的IL指令,并用字典缓存对应关系。一条IL指令分为OpCode和Operand,不得不说dnlib很强大,对两者都进行了分类,并记录了运行与堆栈的方式。按着OperandType进行分类处理:
6.2.1,InlineField、InlineMethd、InlineType、InlineTok这四类Operand对应着FieldDef、ITypeDefOrRef、MemberRef、MethodSpec四种类型,前两种在缓存中直接找到对应的对象替换即可,MethodSpec实际上就是泛型方法的引用,对它需要重新生成新的对象,而MemberRef则略复杂一些,使用一个名为CloneMember的函数,作用除了重新生成引用以外,还要添加对这个MemberRef区分是方法还是字段,并对它的父类的增加引用,所以才会有元数据表中的AssemblyRef与TypeRef表。
6.2.2,InlineVar与ShortInlineVar,这两类则Operand是局部变量和方法参数的读写操作,按着先前缓存的局部变量和创建方法时缓存的方法体参数一一替换就可以了。
6.2.3,InlineSig类型,实际上它就是一个直接引用方法签名跳转到指针的指令,Operand就是一个方法签名,重建它。
6.2.4,InlineBrTarget、ShortInlineBrTarget与InlineSwitch类型,前两个是直接跳转的IL指令,而switch则是一个跳转表。dnlib中跳转指令的Operand并不是Offset数字,而是直接要跳转到的那条指令,也就是目的地Instruction,所以我们需要缓存它,待所有指令都添加完毕以后重建这些跳转的关系。
6.2.5,其它的OperandType就剩下直接操作数据的IL命令了,直接复制它们的Operand就可以,因为它们都是System类型。
6.3,计算Offset并重新建立跳转关系。刚开始我还手动计算每条IL的Offset,后来发现CilBody类里有个名为UpdateInstructionOffsets的方法。按上文的跳转指令缓存和上上条中缓存的所有IL指令来重建对应关系,这里要注意switch的跳转表是Instruction[]类型而其它长短跳指令是普通的Instruction类型。
6.4,异常处理表的重建。其实这里我也没有弄得很明白,但是没有关系,按着缓存的所有IL指令一一创建新的ExecptionHandler加入表中即可。
6.5,此时已完成整个方法体的重建,但是等等,我们似乎漏了什么,当然,被混淆过的方法体需要还原,那些被加密的字符串和数据也需要还原,所以我在这里标了一段注释。
7、根据缓存的程序集、模块、类型、字段、属性、事件、方法、方法参数、泛型参数,创建他们的特性。
8、为模块添加资源,其中包括.NET资源与WIN32资源。
9、增加模块入口点,这里有点遗憾,对于NATIVE代码入口点的程序集我还不知如何处理,主要是因为NATIVE代码的方法还未能重建
10、保存,测试,出错,试调,还出错,经过一段时间,可算成功了。
Dump内存中的Assembly到磁盘