首先写在前面,这篇文章源于个人的研究和探索,由于.NET有自己的反射机制,可以清楚的将源码反射出来,这样你的软件就很容易被破解,当然这篇文章不会说怎么样保护你的软件不被破解,相反是借用一个软件来讲述是怎么被攻破的,也会有人说这是一篇破文,我其实这篇文章已经写了很长时间了,不知道以什么形式发出来,因为毕竟是有些破解类的东西。但是我觉得从这篇文章相反的是能够带来一些启发。大家应该都知道Reflector这个反编译软件还有一个插件是专门用来改IL的插件叫Reflexil,这里我们也要用到前面那个工具Reflector,后面的插件我们这里我们不用,接下来分析的东西可以让大家能够更深入.NET的PE,深入内幕来看看,好,闲话少说….直接上分析。
如果不懂.NET的PE文件结构的帅哥美女,可以看一下上一篇文章http://www.cnblogs.com/dwlsxj/p/4052871.html,这里要用到PE的知识,首先我们来看一下这个程序的限制,这个程序有显示限制也就是显示的列表只能显示三行。如下图所示:
我们现在要用Reflector工具将我们要破解的程序反编译一下看一下哪里没有跳转到MessagBox弹出对话框了!反编译成C#代码后我们可以看到这个程序就一个主界面,看到了Form名字就说明这个是一个Windows的窗体:
点开之后,就发现ProgressChanged方法里面有内容,里面包含了弹出消息框的全部代码,好的我们这里就确定了是这个方法让这个消息框弹出来的!的确他就是罪魁祸首,先记录备案。
现在已经知道是哪个方法弹出消息框来了,那么我们就可以再元数据表中进行查找该方法所在的RVA,这里我要讲一个元数据表中的表这个表就是MethodDef表,这个表很重要也很好玩,这个表里不但指出了该方法的IL代码的位置,还限定了方法的属性。下面来看一下表结构:
偏移 | 大小 | 名称 | 说明 |
0 | 4 | RVA | 该方法体的RVA(方法体包括:方法头、IL代码、异常处理定义) |
4 | 2 | ImplFlags | 限定了方法的执行方法(如abstract、P/Invoke) |
6 | 2 | Flags | 先顶了方法的调用属性和其他的一些性质 |
8 | 2(4) | Name | 指向#String的偏移,表示该方法的名称 |
2(4) | Signature | 指向#Blob的偏移,Signature定义了方法的调用方式(如返回值类型等) | |
2 | ParamList | 指向Param表的索引,指出了方法的参数 |
看到上面这个表不禁让我开心,因为我能找到这个方法存放的位置,也就是我需要的是这个RVA的地址,那么这个方法的RVA是多少呢?带着疑问思考,我们会想到用到一个工具来帮助我们查找这个方法到底在Method的第几个,这里不讲直接打开CFF也可以看到这个方法。我要曲折的找一下。打开ILDASM,既然.NET里面有元数据这个一号人物,我们就来小窥一下元数据表,该方法的元数据肯定在里面。
果然不出我们所料,确实在元数据里面有描述,因为元数据是描述数据的数据,那么我们就拿到了这个Token标示:0600001F,可以翻回到上一篇文章找一下这个对应的表是MethodDef正还是我们想要的,在这个表下的第31的位置就是我们要找的内容,怀着疑问打开CFF软件,来证实一下我们找的没有错!!!
经过证实是我要找的ProgressChanged方法在元数据表中的描述,现在就可以取出关键信息ProgressChanged方法的RVA:0x3184
通过CFF查看一下区块的内容:
可以正确的观察到该方法的RVA存放在.text区块中,因为该区块的范围是:2000~14A00,而3184正好落在了这段地址当中,好,接下来就可以算出该代码在物理地址了:3184-2000+200=1384,好的,0x1384就是我们要在文件中查找的的物理地址。这时候打开16进制编辑器,将程序载入到16进制编辑器中。CTRL+G搜索0x1384这个地址,下面是我们搜到的地址:
你们会疑问我怎么知道这么一段就是这个方法的代码呢?让我来揭晓这个谜底。OK,RVA我们算的肯定没错,也就是开始位置1384这个肯定是没错的,但是代码的长度不是很确定对吧?好,打开ILDASM找到这个方法就知道这个方法的长度,或者是在这个方法的头部我们就可以确定这个方法的长度。
验证结果的时候到了通过ILDASM来验证下:
通过上述结论我们可以证实确实是存放的IL的代码,我们可以看到上面有一个开始指令,是我标记的这个开始指令的IL代码是ldarg.2加载方法参数2到堆栈上。这里不看他的个什么东东!我们只要知道他的Opcode是多少就好了这个指令的Opcode是04,那么我们就在上面的16进制编辑器中进行搜索:
意思就是04这个指令的前面都是在做初始化堆栈和初始化参数的操作,而从04开始才是真正执行代码。好的开始的指令已经找好了,我们就要看一下我们要修改那段代码了,先观察这段比较num--==0这里,注意这里修改代码的时候不能破坏代码的长度和代码的堆栈平衡原理。我们想如何能让这个条件永远不成立,OK,我想到了一个方法就是不让这个参数进行减法操作,让他一直进行加法操作!首先先小窥一下的他的IL代码我们开讲一下整体IL的实现,这里只讲num--==0处的代码:
代码详细讲解如下:
IL_002f: ldloc.3 //加载3到堆栈上。
IL_0030: dup //复制栈顶数据
IL_0031: ldc.i4.1 //加载1到堆栈上
IL_0032: sub //3-1的操作。
IL_0033: stloc.3 //保存到局部变量3中这是由原来的3变成了2
IL_0034: ldc.i4.0 //加载变量0到堆栈
IL_0035: ceq //于O比较后的结果放在堆栈上(返回0或1) IL_0037: ldc.i4.0 //因为我们比较后的结果为0或1所以在压入一个0后面进行比较false(ceq从堆栈弹出两个参数)
IL_0038: ceq
IL_003a: brfalse IL_0113//条件成立跳到消息框
OK分析到这里,这段指令的sub指令是我们的关键,我们要将这个指令改为Add就可以实现了,对应的Sub的Opcode是59,而对应AddOpcode是58这样我们把59变成58再把16进制另存一份就可以实现所有功能了。
改好后我们来看一下效果如何吧!!!
其实我们看似Reflexil简简单单的修改了一个指令的值,其实背后做了大内容,比如要对区块的RVA进行修正,就当前的方法的RVA的修改以及所有方法的修改等操作。这块就不多说了有兴趣的自己研究下!
这东西虽然看着没有Reflexil来得快但是能让你更深入的了解.NET的PE结构,小弟再次声明本文章只是为了研究技术,没有用做商业用途,为了支持正版我已经将标题遮挡住了,通过上述的文章,我有点小小的想法,就是我们可以讲我们需要加密的方法体抽出来,当jit调用时再进行还原这样就会对程序的安全性有一个提升。这里只是我自己的个人看法,还请各位大牛指点小弟,小弟在此谢过了。