如何在旧的 DOS 游戏中找到字符串的来源?

逆向工程 拆卸 调试 字符串
2021-07-09 03:23:48

我正在尝试修改旧 DOS 游戏(FIFA International Soccer)中的一些字符串,特别是球员的姓名。

在过去,在后 DOS 游戏中完成这样的任务并不太难,因为字符串很容易在可执行文件或数据文件中找到。但是,对于这个 DOS 游戏,我被卡住了。

我已经用十六进制编辑器扫描了游戏文件和可执行文件,但找不到字符串。该游戏包含一个english.dat包含可读本地化字符串文件。但是,此文件仅包含游戏标题和菜单选项的名称,而不是我正在寻找的玩家名称。其他文件由其他语言、图形和声音的本地化字符串组成。

我使用 DOSBOX 的调试版本使用以下命令执行内存转储:

memdumpbin 180:0 1000000

我从这里得到命令:http : //www.vogons.org/viewtopic.php?t=9635

在内存转储中,我可以找到我想要更改的字符串。

链接此信息以查找字符串来源的最佳方法是什么?我假设这些字符串在可执行文件中以某种方式加密或压缩,尽管它可能在另一个不起眼的游戏文件中。如果字符串被压缩/加密,也许有一种通用的方法可以将这些数据从可执行文件中提取出来。

我安装了 IDA5(免费版),并且很乐意将其用作流程的一部分。我的操作系统是 Windows 8 / 64 位。

另外,要明确 - 我想修改源(即文件中)而不是内存中的字符串。

2个回答

该程序使用 PharLap DOS 扩展程序,如其 MZ 标头中所示。32 位可执行程序从 offset 开始18A0,根据“重定位表标头内的偏移量”(参见http://www.program-transformation.org/Transform/PcExeFormat),在该位置您可以看到正确的签名P3根据头信息,可执行文件的长度是0x95851,这是另一个提示这是正确的。在这部分的末尾,从 开始18A0,您可以看到一个文本字符串“Hello EA”,在接下来的 32 字节“页面”中MZ,表明嵌入了另一个可执行文件的签名所以这大部分必须包含主要的可执行文件。

使用简单的十六进制编辑器以我喜欢的 16 个十六进制字符的宽度浏览文件时,我注意到在执行向下翻页时重复出现的模式(这是一种“了解”文件包含的数据类型的好方法)。我看到图案每 2 行重复一次,当我将显示宽度设置为 32 时,图案很明显。可执行格式总是以一个固定的头开始,通常后面跟着很多用于填充的零,所以我怀疑重复模式可能是 XOR 键。一个简单的 C 程序证实了这一点;我不知道从哪里开始解码,但 32 的第一个非全零倍数似乎是一个很好的猜测: offset 0x1AA0

从那里解码证明预感是正确的:

00000 : Y...r9..n3.>..-.A@I.7P........h4..a"1.P(s.......x. rG..f...X.+..
00040 : ..a|D.P(.b..A...x......f3..F..h4....a.P(...........o7..f3..F2...
00080 : .@@@@@@...@@BLASTER=@ULTRASND=@GOLD=@mvsound.sys@DEVICEdevice@@@
000C0 : @@@@@@@@@@@.......ULTRAMID@@@@@@@@@@@@@@@ ..@.@@@@@@.........@.@
00100 : .@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@@.@@@@..@@...@@@..@@......&...
00140 : ./....8....C....N....X...@c.@@ m....y...................C.......
00180 : .PCSPKR.ADV@MT32MPU.ADV@ADLIB.ADV@ADLIBG.ADV@SBFM.ADV@SBFM.ADV@S
001C0 : BP1FM.ADV@SBP2FM.ADV@PASFM.ADV@PASOPL.ADV@TANDY.ADV@GF1MIDI.ADV@
00200 : CUST_MID.ADV@SBP2FM.ADV@SBAWE32.ADV@@@@ALGDIG.ADV@SBDIG.ADV@SBDI
00240 : G.ADV@SBPDIG.ADV@SBPDIG.ADV@PASDIG.ADV@PASDIG.ADV@@GF1DIGI.ADV@C
(etc.)

所以下一步是向下滚动到这部分的末尾,看看那里有什么。灾难!我看到的不是可读文本,而是随机数据——但仍然具有清晰的模式。

但是“可执行文件”不是一个连续的长数据块。通常看到它被分为“可执行代码”、“初始化数据”、“未初始化数据”、“重定位”等单独的部分。当加载到内存中时,这些部分都从对齐的地址开始,但不一定在文件本身中,或者具有相同的“内存页面”大小。因此,XOR 加密可能会在新部分的开头重新开始。PharLap 标头应包含有关每个部分开始和结束位置的信息(如果您要尝试调整程序,则应查看此内容),但要确认 XOR 键是否相同,我只需调整起始位置。进一步开始一个位置,没有成功,

890C0 : B.@.L.@^W.@.a.@^l.@.v.@^..@...@ @ @ @ @ @ @ @~FIFA International
89100 :  Soccer@ @PC Version by@~The Creative Assembly@ @~Lead Programme
89140 : r@ @Tim Ansell@ @ @ @~Programmers@ @Adrian Panton@Clive Gratton@
89180 :  @ @~Lead Artist@ @Will Hallsworth@ @ @ @~Additional Artwork@ @A
891C0 : lan Ansell@ @ @ @~Original Music@Composed, Produced@and Performe
89200 : d by@Ray Deefholts@for ~HFC Music@ @Additional Drum@Programming 
89240 : and@Assistance@ @Tim Ansell@ @~Sound Effects@ @Bill Lusty@ @ @ @
89280 : ~Producer@ @Kevin Buckner@ @ @ @~Associate Producer@ @Nick Golds
(etc.)

这就是我需要的证据:数据部分确实使用了相同的 XOR 密钥。下一步:测试从 0 到 31 的所有可能性,看看是否会出现一些情况。只有在 +30 结果才奏效,就像我要放弃一样:

782C0 : ..@...@,..@..Algeria@Ali Mehdaoui Igail@Mohammed Said@Abdel Dahb
78300 : i@Hamid Ahkmar@Nagar Baltuni@Omar Mahjabi@Ali Cherif@Hamar Mahbo
78340 : ud@Khered Adjali@Imahd Tasfarouk@Alamar Sahid@Mahmar Ahboud@Akha
78380 : r Binnet@Mouhrad Dahlib@Mahied Amruk@Lakhar Diziri@Amaar Azir@Mu
783C0 : stafa Farai@Akmar Bahoud@Ahmad Said@Taraki Aziz@Argentina@Alfio 
(etc.)

因此,可执行文件中的每个单独部分都使用 32 字节的 XOR 密钥进行加密;这个 XOR 键对于所有部分都是相同的它从每个部分开始一个新的。

下面的 C 程序将解密整个文件,您必须手动调整起始位置。要编辑文件,您必须:

  1. 阅读 PharLap 的部分。
  2. 单独解密每个部分。
  3. 全部写入一个新文件。
  4. 调整你想要的。
  5. 再次加密这些部分(它是一个 XOR 密钥,所以它使用完全相同的算法)。
  6. 将加密文件复制回主可执行文件。

关于#4 的注释:你提到改变球员的名字。由于它的名字以零结尾的列表,你可以假设有指针的列表,以这些名称在其他地方。这意味着您只能更改名称的单个字符——而不能使其更长。如果您想自由调整所有名称,则必须找到指针列表并进行调整。


(初步更新)

  1. XOR 编码不使用节。相反,似乎每个块都以一个确定其长度的单词开头,可能还有 1 或 2 个下一个单词(可能(再次)设置 XOR 键的起始位置)。目前还没有定论。

  2. 可执行文件有很多零。如果您计算每个 32 字节块中零的数量,对所有 32 个可能的位置进行异或,并打印出具有最高数量的异或位置,您可以看到相同“最佳”猜测的连续列表。这表明有更长和更短的部分与相同的密钥异或,可能有助于确定长度算法。


#include <stdio.h>
#include <stdlib.h>

unsigned char encrypt[32] = {
    0x23, 0x91, 0xC8, 0xE4, 0x72, 0x39, 0x9C, 0xCE,
    0x67, 0x33, 0x99, 0xCC, 0xE6, 0x73, 0xB9, 0x5C,
    0x2E, 0x17, 0x8B, 0x45, 0xA2, 0x51, 0xA8, 0x54,
    0x2A, 0x95, 0xCA, 0x65, 0x32, 0x19, 0x8C, 0x46
};

int main(int argc, char *argv[])
{
    FILE *f;
    int i, c, d = 0;

    f = fopen ("../Downloads/fifa/fifa.exe", "rb");
    if (!f)
    {
        printf ("yeah no such file\n");
        return 0;
    }

    /* reasonable assumption for start: */
    fseek (f, 0x1aa0, SEEK_SET);
    /* adjust per section! this position is valid for the names only */
    fseek (f, 30, SEEK_CUR);
    c = 0;
    printf ("%05X : ", d);
    do
    {
        d++;
        i = fgetc (f);
        if (i == EOF) break;
        i ^= encrypt[c & 31];
        if (i >= ' ' && i <= '~') putchar (i); else if (i) putchar ('.'); else putchar ('@');
        if (++c >= 64)
        {
            c = 0;
            printf ("\n");
            printf ("%05X : ", d);
        }
    } while (d < 0x95851);
    fclose (f);

    return 0;
}

有一个ida 插件可以连接到(修补版本的)DosBox,并允许您从 ida 调试 DosBox 游戏。但是,我怀疑您是否可以将其与 ida 的免费版本一起使用。

您的 memdumpbin 使用地址180:0提示游戏使用了 dos 扩展程序这一事实,因此“真正的”程序是在保护模式下运行的 32 位程序 - 180 是 dos 扩展程序通常用于其 32 位段的段值。这带来了另一个问题 - IDA 5 free 可以读取 16 位 .exe 的 dos 部分(基本上是扩展程序),但不能读取 32 位部分(其格式称为 LE)。

然而,更旧的 IDA Free 4.1 能够读取 LE 文件,它仍然在互联网上流传(搜索 idafre41.zip)。还有一个免费的 dos 扩展器DOS32A,它包含一个工具(您需要下载源代码),可以将 32 位 LE 文件与 16 位扩展器分开。一年前我想调试一个类似的程序时,我能够使用 DOS32A 获取 LE 文件,将其加载到 IDA 4.1 中以获取 IDA 数据库,并在 IDA free 5.0 中打开该数据库。(我现在买了 IDA,这让我的生活变得更轻松;尽管价格对业余爱好者来说有点贵,但我还是会向任何人推荐这个)

您也许可以将找到播放器名称的地址与 IDA 中的 XREF 匹配,然后检查访问该地址的函数是否写入了该地址,然后使用集成在 DOS32A 中的调试器来调试您的程序并验证播放器的位置名字被写入,最后追踪它们的来源并修改它。但是,如果名称确实在原始 .exe 文件中被压缩,则您必须从程序集文本(可能是也可能不是标准的)中识别压缩方案,然后找出如何根据到那个方案,并希望新的字符串适合旧的压缩字符串使用的内存。

所以,你的任务不会很容易,但你会在这个过程中学到很多东西 - 祝你好运!