逆向工程难以破解的视频游戏保存文件校验和

逆向工程 二元分析 静态分析 十六进制 密码学
2021-06-20 08:42:55

一段时间以来一直在尝试解决这个问题,并且永远感谢任何在校验和算法方面有更多经验的人伸出援手。

很长一段时间以来,我一直在尝试更改旧的 Playstation 2 游戏的保存文件。但是,这种保存受到三重双向校验和的保护。

如果您好奇,这款游戏是Fire Pro Wrestling Returns

幸运的是,我已经绘制了大量的保存数据,其中大部分是玩家可以很容易地影响自己的东西(因为其中大部分是用户创建的角色),这有很大帮助。

我已有的数据如下:

没有校验和的保存数据本身正好是 888 KB,其中最后 400 字节似乎是空填充以完善文件大小。

在这 888 kb 之后,共有 4580 字节的校验和,分为三个块。

通过游戏更改保存数据并重新保存的实验,通过比较保存文件,表明校验和的工作方式如下:

第一个块是 3552 字节,由 888 个 4 字节校验和组成,每个校验和用于 888 KB 的保存数据。这已经通过更改单个字节进行了测试和验证。它们似乎也没有相互解决 - 最后有几个块,我能够使两个块彼此相同,并且它们具有相同的校验和。所以希望这意味着每个块都是在真空中计算的,而不是依赖于以前的结果。

第二个块是1024字节,似乎是主保存数据的交叉校验和(所以这个校验和的第一个字节是888个“1kb块”的保存数据的每个第一个字节的校验和)。我已经通过在各个地方更改单个字节来测试并似乎验证了这个理论,并且能够完全预测哪些校验和字节会因此而改变。

第三个块是另一个 4 字节校验和。由于第一块校验和是将 1024 字节数据转换为 4 字节校验和的校验和,而整个第二块校验和是 1024 字节,我在这里猜测这是通过对第二个校验和块,使用与块 1 相同的算法。

所以,基本上似乎有两种校验和算法在工作,其中一种我可以完全控制输入数据,第二种我的输入有限,因为并非所有的保存文件都可以轻松操作,这取自它的所有部分。

以下是块 1 中不同数据块生成的 4 字节校验和的一些示例:

BB 6B 82 F3 = all zeroes  
68 8D C8 0C = 01 followed by all zeroes  
B4 D9 DC B4 = 02 followed by all zeroes  
DF 5F 6E 0D = all zeroes with 01 at 0x60  
78 DF C3 08 = all zeroes ending with 01 

我的问题是,在以二进制、十六进制或十进制查看它时,我没有发现任何可辨别的模式。我已经开始尝试对它的汇编程序进行逆向工程,看看我是否能找到这样的算法,但考虑到软件的大小(以及可能或可能的 PS2 多核架构),这并不是一项简单的任务不参与)。

我在映射第二个校验和方面所做的工作较少,但是我尝试通过添加或减去一个来更改内部的单个字节,并注意到在大多数情况下,校验和仅改变了一步(虽然在不同的方向),但现在我专注于第一个算法,因为那个感觉像是更大的头疼。

如果提供更多的实验数据有帮助,我很乐意这样做。=)

虽然最终结果绝对是我​​开始研究这个的原因,但我的兴趣主要不是解决方案,而是了解如何达到它的方法。

更新:
感谢一些非常有用的评论,我得到了一些调试设置,虽然我不确定我找到了正确的代码......我在加载保存游戏时记录了所有内容(因为它需要为加载时进行比较)并查找带有子程序的重复代码块。

找到要引用的正确程序集确实是我在所有这些帮助之后遇到的最大麻烦,而我正在努力学习如何解决这个问题......

例如,这个块在我的日志中重复了 104 次(这是部分加载,不是整个事情,因为我今天早上上班前没有太多时间),尽管我需要将它翻译成伪代码,看看它是否是相关的:

0012f5d4 0c04c77c: JAL    , 00131df0, 001c0b04 (ra),  
0012f5d8 24a5ffff: ADDIU  , 00000000_00000004 (a1), 00000004 (a1), ffff (65535),  
00131df0 30820008: ANDI   , 00000000_00002000 (v0), 00000000_0000211c (a0), 0008 (8),  
00131df4 10400008: BEQ    , 00000000_00000008 (v0), 00000000_00000000 (r0), 00131e18,  
00131df8 30820010: ANDI   , 00000000_00000008 (v0), 00000000_0000211c (a0), 0010 (16),  
00131dfc 10400004: BEQ    , 00000000_00000010 (v0), 00000000_00000000 (r0), 00131e10,  
00131e00 00051040: SLL    , 00000000_00000010 (v0), 00000003 (a1), 01 (1),  
00131e04 00451021: ADDU   , 00000000_00000006 (v0), 00000006 (v0), 00000003 (a1),  
00131e08 03e00008: JR     , 0012f5dc (ra),  

再次编辑添加:

当我有时间坐下来时,希望今晚,一个计划是获取一个我知道有效的保存文件,然后在第一个块的第三个校验和中引入错误,然后使用调试器运行加载序列再次查看失败的地方,看看是否可以缩小第一个校验和块和所涉及的例程。

1个回答

像其他人一样,我建议尝试获取计算校验和的汇编代码。如果你得到了,剩下的就很容易了。

然而,有时这很难获得,所以这里有一些关于没有任何代码的逆向工程校验和的提示。

  1. 请记住,校验和算法是工程师做出的设计选择,因此请考虑可能促成此选择的约束和实践。

    • 它是较旧的硬件,因此效率可能是一个问题 - 没有人愿意等待 2 分钟来保存/加载游戏。这意味着位移、异或和查找表比复杂的分支、乘法、除法或任何涉及浮点数的东西更有可能。该算法也可能对数据进行一次传递。
    • 该算法可能与同一工作室或同一程序员在其他游戏中的校验和算法相同或不同。
    • 该算法可能是现成的算法(如 MD5 - 设计于 1991 年,因此早于游戏)
    • 该算法可能基于 Sony 发布的校验和指南或在游戏设计书籍或类似书籍中发布的校验和指南。
    • 显然,游戏的发布日期对于调查这一领域至关重要
  2. 熟悉最重要的校验和/哈希算法,尤其是游戏发布时流行的算法。

    • 严格来说算法是一个哈希函数,因为明文的微小变化完全改变了哈希值
    • 在您的数据上尝试创建游戏时存在的各种散列函数(例如 MD5),看看是否匹配。执行此操作时请注意代码中的错误(例如,将字符串 '\x00' 发送到哈希函数而不是 NUL 字节之类的愚蠢错误)
    • 了解常见哈希函数的工作原理有助于您猜测未知函数的工作原理。如果这个算法是内部开发的,它可能比当时的主要哈希函数更简单——或者它可能与 MD5 完全相同,但使用不同的常量。这将很难逆向工程,特别是如果您不能自动运行数百万个测试用例。
    • 许多散列算法(包括 MD5 和 SHA-1)对数据进行一次传递。MD5 和 SHA-1 都以 64 字节的块处理消息
    • 假设我们正在反转的算法是这样,这意味着对消息的最后两个块的更改可能会产生最多的信息,因为对早期块的更改的影响可能很快就会“乱码”。例如,您可能希望在消息的最后一个块中进行 1 位更改并查看它产生的异或差异,然后对消息的倒数第二个块进行 1 位更改,然后再次进行最后一个块中相同的 1 位更改,并查看它产生的 XOR 差异。或者您可以对最后一个块进行大量的 1 位更改,看看它是否有任何模式。请注意,MD5 和 SHA-1 都在消息的末尾附加了一个额外的位,在这种情况下,这会在末尾添加一个额外的块,这会使问题复杂化。
  3. 也许有人已经做到了?

    • 谷歌搜索你提供的全 0 哈希值,我发现了这个:http : //www.fireproclub.com/forum/viewtopic.php? f=12&t=1860
    • 看起来他们没有弄清楚,但也许其他人已经(并且可能将其放入开源工具中),或者您可以联系游戏团队的程序员,询问他们等。

倒车快乐!