逆向工程压缩文件,从哪里开始?

逆向工程 文件格式
2021-07-06 03:39:13

我想打开芬兰体育联盟用于簿记的“数据文件”。它包括了几十年的所有统计数据,所以它是一个有趣的数据文件。

文件在这里:http : //www.bittilahde.fi/Tietokanta.dat(英文Database.dat)

簿记程序在这里:http : //www.pesistulokset.fi/Kirjaus504.exe

我发现了什么:

  • 数据库文件的直方图完全平坦
  • 数据库文件中没有我可以识别的标题
  • .exe 是用 Delphi 4 编译的
  • 我可以找到一些带有IDR 的数据结构,但无法弄清楚如何解压缩文件。

下一步可能是什么?

4个回答

OllyDbg 中查看它似乎是一项繁重的任务。看起来像一个带有加密和(自定义?)压缩数据的自定义数据库。在这样的应用中通常就是这种情况。具有结构化数据的平面文件不属于该文件的一部分。

无论如何。作为初学者:

在尝试了一些通用压缩工具(如 7z 或 binwalk)(尚未测试)后,可以快速检查一下,可以使用来自 Sysinternals 的ProcMon启动 ProcMon,然后启动您的应用程序并在 ProcMon 中的应用程序上设置过滤器。你很快就会发现:

简而言之,它读取不同大小的块,但对于主要数据处理,它读取 16384 字节的块。过程分步骤:

  1. 生成 256 个整数的种子图。(在应用程序启动时完成一次。)
  2. 循环:
    2.1 从 .dat 文件中将 16384 字节读入缓冲区。
    2.2 缓冲区上的异或例程使用偏移量和缓冲区的最后四个字节作为基数。
    2.3 使用步骤 1 中的种子映射对 XOR 缓冲区进行校验和。
    2.4 解析缓冲区并读出数据。

应用程序还会多次读取相同的块。


2.1:

例子:

013D0010  D4 9E BE BF 1C 1C 0B D4 C5 E7 11 B5 09 48 87 FA  Ôž¾¿ÔÅçµ.H‡ú
013D0020  29 4C 03 C9 DE 4A 2B 71 74 7F D2 48 E7 13 94 4E  )LÉÞJ+qtÒHç”N
...
013D3FF0  6A D1 55 92 E2 16 60 53 69 89 86 7D D9 D8 10 BC  jÑU’â`Si‰†}Ùؼ
013D4000  90 F3 D1 48 28 47 34 EC 39 36 EC 4D 69 2A 7D E5  óÑH(G4ì96ìMi*}å
                                             |_____._____|
                                                   | 
                         Last DWORD aka checksum --+

按发现顺序排列的步骤和详细信息:

将 .dat 文件拆分为 16384 字节的块,并生成每个文件的十六进制转储,以便于搜索和比较。说实话,我采用Linux对于这部分有ddxxd -psgrepdiff等。

启动 OllyDbg,打开应用,定位CreateFile并设置断点:

00401220   $-FF25 18825000  JMP DWORD PTR DS:[<&kernel32.CreateFileA>;  kernel32.CreateFileA

F9直到文件名 (in EAX) 是 .dat 文件。在 上设置/启用断点ReadFileF9阅读完成后,开始逐步查看完成的内容。


看着它:

2.2:

读取后,首先使用偏移量作为“魔术”修改缓冲区,从以下位置开始:

0045F5EC  /$ 53   PUSH EBX     ;  ALGO 2: XOR algorithm - post file read.
...
0045F6B6  \. C3   RETN         ;  ALGO 2: RETN

至少采取的两个操作似乎是libj_randl1()libj_randl2()(这将是上面列表中的步骤 2.2。)

简化:

edx = memory address of buffer
ecx = offset / 0x4000
edi = edx
ebx = ecx * 0x9b9
esi = last dword of buffer & 0x7fffffff
ecx = 0

i = 0;
while (i < 0x3ffc) { /* size of buffer - 4 */
    manipulate buffer
}

整个例程转换为 C 代码:

int xor_buf(uint8_t *buf, long offset, long buf_size)
{
    int32_t eax;
    int32_t ebx;
    int32_t esi;
    long i;

    buf_size -= 4;

    ebx = (offset / 0x4000) * 0x9b9;
    /* Intel int 32 */
    esi = (
        (buf[buf_size + 3] << 24) |
        (buf[buf_size + 2] << 16) |
        (buf[buf_size + 1] <<  8) |
         buf[buf_size + 0]
        ) & 0x7fffffff;

    for (i = 0; i < buf_size /*0x3ffc*/; ++i) {
        /* libj_randl2(sn) Ref. link above. */
        ebx = ((ebx % 0x0d1a4) * 0x9c4e) - ((ebx / 0x0d1a4) * 0x2fb3);

        if (ebx < 0) {
            ebx += 0x7fffffab;
        }

        /* libj_randl1(sn) Ref. link above. */
        esi = ((esi % 0x0ce26) * 0x9ef4) - ((esi / 0x0ce26) * 0x0ecf);

        if (esi < 0) {
            esi += 0x7fffff07;
        }

        eax = ebx - 0x7fffffab + esi;

        if (eax < 1) {
            eax += 0x7fffffaa;
        }

        /* Modify three next bytes. */    
        buf[i] ^= (eax >> 0x03) & 0xff;

        if (++i <= buf_size) {
            buf[i] ^= (eax >> 0x0d) & 0xff;
        }
        if (++i <= buf_size) {
            buf[i] ^= (eax >> 0x17) & 0xff;
        }
    }

    return 0;
}

然后生成结果缓冲区的校验和(减去最后一个 dword),并根据最后一个 dword 进行检查。这里它使用了一个来自 BSS 段的缓冲区,该缓冲区是在启动时生成的,步骤 1. 来自上面的列表(偏移0x00505000+0x894并使用一个区域,4 * 0x100因为它是 256 个 32 位整数)。这个种子图似乎是恒定的(从不重新生成/更改),如果不想验证缓冲区可以跳过。

1.

反汇编中的代码点(我的评论):

0045E614 . 53   PUSH EBX           ;  ALGO 1: GENERATE CHECKSUM MAGICK BSS
...
0045E672 . C3   RETN               ;  ALGO 1: RETN

BSS 编号的代码可以简化为用 C 编写,例如:

int eax;    /* INT NR 1, next generated number to save */
int i, j;

unsigned int bss[0x100] = {0};  /* offset 00505894 */

for (i = 0; i < 0x100; ++i) {
    eax = i << 0x18;
    for (j = 0; j < 8; ++j) {
        if (eax & 0x80000000) {
            eax = (eax + eax) ^ 0x4c11db7;
        } else {
            eax <<= 1;
        }
    }
    bss[i] = eax;
}

2.3:

该 bss int 数组用于操作缓冲区以生成校验和,该校验和应等于从文件读取的 16384 字节中的最后一个整数。(最后一个双字,在校验和例程和异或运算中跳过的那个。)这将是上面列表中的步骤 2.3。

unsigned char *buf = manipulated file buffer;
unsigned char *bss = memory dump 0x00505894 - 0x00505C90, or from code above

eax = 0x13d0010;  /* Memory location for buffer. */
edx = 0x3ffc;     /* Size of buffer - 4 bytes (checksum). */

...

退出时ecx等于校验和。

反汇编中的代码点(我的评论):

0045E5A8  /$ 53  PUSH EBX    ;  ALGO 3: CALCULATE CHECKSUM AFTER ALGORITHM 2
...
0045E5E0  \. C3  RETN        ;  ALGO 3: RETN (EAX=CHECKSUM == BUFFER LAST 4 BYTES)

缩短为 C 例程,它可能是这样的:

int32_t checksum(int32_t map[0x100], uint8_t *buf, long len)
{
    int i;
    int32_t k, cs = 0;

    for (i = 0; i < len; ++i) {
        k = (cs >> 0x18) & 0xff;
        cs = map[buf[i] ^ k] ^ (cs << 0x08);
    }

    return cs;
}

检查为 OK,然后缓冲区中的校验和设置为:两个最低有效字节 = 0,两个最高有效字节设置为某个数字(文件中的块号或读取号,(从 0 开始))。

0045F9BF   . C680 FC3F0000 >MOV BYTE PTR DS:[EAX+3FFC],0     ;  Set two lower bytes of checksum in dat buf to 0
0045F9C6   . C680 FD3F0000 >MOV BYTE PTR DS:[EAX+3FFD],0     ;  follows previous
0045F9CD   . 66:8B4D F8     MOV CX,WORD PTR SS:[EBP-8]       ;  Set CX to stack pointer value of addr EBP - 8 (counter of sorts)
0045F9D1   . 66:8988 FE3F00>MOV WORD PTR DS:[EAX+3FFE],CX    ;  Set .dat buffer higher bytes like CX.

现在完成所有这些之后,实际的数据复制开始于更多的算法。真正的工作开始了识别数据类型、结构、位置和内容等。找到了一些提取名称等的例程。但是芬兰语的所有内容都无助于使其更容易掌握;)。

上面的数据可能是一个开始。

开始时可能会感兴趣的一些断点:

Breakpoints
Address    Module     Active    Disassembly  Comment
0045E5A8   Kirjaus5   Disabled  PUSH EBX     ALGO 3: CALCULATE CHECKSUM AFTER ALGORITHM 2
0045E5E0   Kirjaus5   Disabled  RETN         ALGO 3: RETN (EAX=CHECKSUM == BUFFER LAST 4 BYTES)
0045E614   Kirjaus5   Disabled  PUSH EBX     ALGO 1: GENERATE CHECKSUM MAGIC BSS
0045E672   Kirjaus5   Disabled  RETN         ALGO 1: RETN
0045F5EC   Kirjaus5   Disabled  PUSH EBX     ALGO 2: FILE POST XOR READ ALGORITHM
0045F6B6   Kirjaus5   Disabled  RETN         ALGO 2: RETN

一些注意事项:

保留您正在使用的 .dat 文件的备份。如果您中止应用程序,文件通常会被损坏,如@blabb所述,请将数据写回文件。.dat 文件似乎也处于活动状态,因此新下载它会产生不同的数据。

您发布的 .dat 文件每次被您的应用程序访问时都会被写回 9.6 MB (0x267 * 0x4000 ) = 0x99c0000 字节

4000个字节在每个相继的访问读取267倍的ReadFile完成每个4000个字节的使用程序(自定义??)xorred然后检查求和 for (int i = 0; i< 0x267 ;i++) { checksum = *(DWORD *) (FilePointer)(0x3ffc + i) } 上死通过16进制软件打开.dat文件所以双字0x3ffc,0x7ffc .... .. 包含最新的校验和

您可能需要对异或校验例程和校验和例程进行逆向工程

用这个断点分析windbg生成的日志文件中的所有调用栈

C:\>cdb -c ".logopen kir.txt;bu ntdll!NtReadFile \".if ( poi(esp+0x1c) != 0x400 0) {gc} .else {kb;g }\"" Kirjaus504.exe

binwalk是一个固件分析工具。它带有一些名为magic 的默认文件签名有一个名为compressed魔法文件,它会扫描常见的压缩签名。显示了很多误报,但它可能会找到一些常见的压缩类型,并且还应该能够解压缩它们。

从现在开始,最好的办法是反转程序的数据库加载功能,因为数据库可能是自定义的。在加载数据库时CreateFile/ OpenFileAPI上放置一个断点,然后查找数据库操作函数以查看它如何提取内容。由于用户界面是芬兰语,因此无法在您发布的程序上尝试此操作。