10.3.2 内存校验
磁盘文件完整性校验可以抵抗解密者直接修改磁盘文件,但对于内存补丁却没有效果,因此必须对内存的关键代码也实行校验。
1.对整个代码数据校验
毎个程序至少有一个代码区块和数据区块。数据区块属性可读写,程序运行时全局变量通常会放在这里,这些变量数据会动态变化,因此校验这部分是没有意义的。而代码区块属性只读,存放的是程序代码,在程序运行过程中数据是不会变化的,因此用这部分进行内存校验是可行的。
具体实现的思路如下:
(1)从内存映像得到PE相关数据,如代码区块的RVA值和内存大小等;
(2)根据得到的代码区块的RVA值和内存大小,计算其内存数据的CRC-32值;
(3)读取自身文件先前储存的CRC-32值(PE文件头前一个字段),这个值是通过光盘映像文件中提供的add2memcrc32.exe写进去的;
(4)比较两个CRC-32值。
这样就实现了内存映像的代码区块校验,只要内存数据被修改,都能被发现。这个方法还能有效地抵抗调试器的普通断点,因为调试器一般通过给应用程序代码硬加INT3指令(机器码CCh)来实现中断,这样就改变了代码区块的数据,计算CRC-32值就会与原来的不同,如图10.50所示。当然用硬件断点不会影响校验值,因为其用了DR3~DR0寄存器,没改变源程序代码数据。
图10.50 计算CRC-32值代码
PE文件在磁盘中的数据结构布局和内存中的数据结构布局是一样的,代码区块在磁盘中的数据与内存映像数据是相同的。Add2memcrc32.exe就是根据这个原理计算磁盘文件的代码区块CRC-32值,并写入目标文件里的。
如果程序不加壳这样就可直接发行了,伹如果用加壳程序来进一步保护时,可能会出错。因为之前是直接从磁盘文件中读取代码区块的RVA值和大小,加壳后,程序读取的是外壳的代码区块RVA值和大小,这样计算出来的CRC-32校验值当然就不对了。解决办法是编程时直接用代码区块的RVA具体值参与计算,这些具体的值可以用PE工具(如Lord PE)查看。由图10.51可知,代码区块(.text)的RVA值为1000h,大小为36AEh,将这些值填进源程序中再编译即可。
图10.51 内存区示意图
虽然源程序一样,但在不同系统编译,代码区块的大小可能会不同,以当时编译的具体值为准。为了方便加壳,改进后的代码如图10.52所示。
图10.52 计算CRC-32值部分改进代码
2.校验内存代码片段
在实际过程中,有时只需对一小段代码进行内存校验,以防止调试工具的INT3断点。实现代码如图10.53所示。
图10.53 小段内存校验(a)
上述代码中CRC32()函数的返回值可通过调试器跟踪得到,再填进源代码里重新编译即可。具体的汇编代码如图10.54所示。
图10.54 小段内存校验(b)
在跟踪调试时,如对401014h~40102Bh之间代码设INT3断点时,CRC校验将发生变化,从而发现程序被跟踪。实际操作时,可以不提示断点被发现,而是悄悄退出,使得校验更隐蔽。