再探.NET的PE文件结构(安全篇)
首先写在前面,这篇文章源于个人的研究和探索,由于.NET有自己的反射机制。可以清楚的将源代码反射出来。这样你的软件就非常easy被破解。当然这篇文章不会说怎么样保护你的软件不被破解。相反是借用一个软件来讲述是怎么被攻破的。也会有人说这是一篇破文,我事实上这篇文章已经写了非常长时间了。不知道以什么形式发出来,由于毕竟是有些破解类的东西。可是我认为从这篇文章相反的是可以带来一些启示。大家应该都知道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进制另存一份就能够实现全部功能了。
改好后我们来看一下效果怎样吧!!!