第63章 其他的事情

63.1 总则

逆向工程人员应当尽可能地以编程人员的角度来分析问题。要理解他们的观点、并且时常扪心自问:如果自己是编程人员的话,自己会如何设计程序。

63.2 C++

在分析C++的class时,本书51.1.5节中讲到的RTTI数据可能就是分析的重点。

63.3 部分二进制文件的特征

在十六进制的编辑器里,16位/32位/64位数据数组的特征十分明显。本节以一个很简单的MIPS程序为例。前文介绍过:每个MIPS指令都是整齐的32位(即4字节)指令。当然ARM模式或者ARM64模式的程序也有这个特点。因此,在形式上这些程序的指令都构成了某种32位数组。

图63.1所示的屏幕截图,就表现出这种数组特征。笔者添加了3条红色的垂直线,以突出这种现象:

6301{}

图63.1 Hiew:一个非常典型的MIPS代码

此外,本书第86章介绍了另外一个典型的例子。

63.4 内存“快照”对比

这种内存“快照”的对比技术可以凸显出内存中变化的数据,过去就常见于8位游戏的作弊领域。

如果您在老式的8位游戏机(这些机器通常内存不大,而游戏本身占用的内存更少)上启动一个游戏,您就可能看到部分游戏数据—例如,现在装有100颗子弹。这时你就可以将所有的内存空间做一个镜像。把镜像存为文件后,您可以先开一枪,这时子弹的剩余数量会显示为99发。这个时候,你再对整个内存空间做另外一个影像。我们对这两个内存影像进行比对,肯定某个字节的数值从100降为99。

考虑到这些8位的游戏程序一般都是汇编语言程序,而且各变量都是全局变量。因此基于以上的对比结果,我们就能分析出是哪个内存地址存放着子弹的数量。有了这个关键的突破点,我们接着就可以在游戏程序的汇编代码(需要进行反汇编处理)中搜索那些指令引用了这个地址,就不难发现对子弹总数进行递减运算的那条指令。然后,我们把相关指令换成NOP,就能保证子弹总数保持100不变。

在8位游戏机上运行的游戏程序,总是会被加载在固定的内存地址。此外,同款游戏的发行版本也比较固定(一旦热销也就不会再进行什么升级了)。因此铁杆玩家们都知道改写哪些地址就能“黑掉”这些游戏了。他们通常会用BASIC语言的单字节赋值指令POKE直接向已知地址写入特定字节。甚至在一些杂志中,常常会出现一些关于8位机的POKE指令列表。

与此类似,修改游戏分数排行榜也比较容易,而且它不仅仅适用于8位游戏。首先,记录一下自己的游戏得分并将存盘文件备份出来。当排行榜的总分出现变化时,再备份一次存盘文件。接下来,我们可以用DOS系统自带的二进制文件比较工具FC直接比对两个文件(存盘文件是二进制文件)。两个文件肯定有几个字节发生了变化,直接修改这些数值就可以调整游戏得分。然而,现在的游戏开发团队通常都非常清楚这些伎俩,因此可能会开发一些程序来应对这些措施。

其他类似的例子可以在本书的第85章中找到。

63.4.1 Windows注册表

在安装一个程序以后,我们就能比对安装前后Windows的注册表。这是一个非常流行的方法,可以用来确认特定程序使用了哪些注册表键值。这也许就是“Windows注册表清理”程序颇为流行的原因。

63.4.2 瞬变比较器Blink-comparator

在介绍了文件比对和内存快照比对的方法之后,笔者不禁想起了瞬变比较器/Blink-comparator (https://en.wikipedia.org/wiki/Blink_comparator

):过去,它曾是天文学家观测天体移动的一种相片比对设备。它能够快速地切换不同时期拍摄的照片,以便天文学家通过肉眼快速地发现两图之间的区别。

实际上,正是借助于瞬变比较器,人们才在1930年发现了冥王星。