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

文件位于:http://www.bittilahde.fi/Tietokanta.dat(英语为Database.dat)

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

我发现的内容:


数据库文件的直方图是完全平坦的
数据库文件中没有我能识别的标头
.exe是用Delphi 4编译的
我可以用IDR找到一些数据结构,但找不到找出如何解压缩文件。

下一步可能是什么?

评论

您尝试过binwalk吗?

@bueno不,我是新来的人,所以我从未听说过。您是否对此进行了改编:code.google.com/p/binwalk

是的,它将搜寻文件以查找各种不同的文件类型。我认为它需要python和libmagic。这是好东西。

@bueno看起来很不错,但是在这种情况下,它并没有告诉我任何有用的信息。熵图给出了整个文件的平面1.0。

我添加了binwalk作为答案,因为它可以帮助其他有类似问题的人。有关熵图和压缩的一些其他信息可以在这里找到:devttys0.com/2013/06/…

#1 楼

在OllyDbg中查看它似乎是一项繁重的任务。看起来像一个具有加密和(自定义?)压缩数据的自定义数据库。在此类应用中通常是这种情况。具有结构化数据的平面文件不属于此文件。
首先:
试用了一些通用压缩工具(例如7z或binwalk)(尚未测试)后,可以快速使用Sysinternals的ProcMon。启动ProcMon,然后启动您的应用程序,并在ProcMon中对该应用程序设置过滤器。您很快就会发现:
简而言之,它读取大小各异的块,但是对于主数据处理,它读取16384个字节的块。该过程分步骤进行:

生成256个整数的种子映射。 (在应用程序启动时完成一次。)
循环:
2.1从.dat文件读取16384字节到缓冲区。
2.2使用偏移量将缓冲区的最后四个字节作为基数,对缓冲区进行XOR例程。
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直到文件名(在EAX中)是.dat文件。在ReadFile上设置/启用断点。 F9,读取完成后,开始步进并查看已完成的操作。

看一下它:
2.2:
读取后,首先将offset用作“ magic”来修改缓冲区“开始于:
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代码:在这里,它使用来自BSS段的缓冲区,该缓冲区是启动时从上面列表中的步骤1生成的。 (偏移0x00505000 + 0x894,并使用4 * 0x100的区域,因为它是256个32位整数)。此种子映射似乎是恒定的(永远不会重新生成/更改),如果不想验证缓冲区,可以跳过。
1.
反汇编中的代码点(注释我的代码):
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;
}

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

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


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

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;
}

在出口ecx等于校验和。
反汇编中的代码点(注释矿山):
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). */

...
< br缩短为C例程,可能是这样的:

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


被检查是否正常,然后将缓冲区中的校验和设置为:两个最低有效字节= 0,将两个最高有效字节设置为某个数字(文件或读取的数字中的块编号,(从0开始)。)
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;
}


现在所有这些操作完成之后,即可进行实际复制的数据以更多的算法开始。真正的工作从这里开始。识别数据类型,结构,位置和内容等。找到一些提取名称等的例程。但是芬兰语的所有内容都无法使它更容易掌握;)。
上面的数据可能只是一个开始。 />可能以以下内容开头的一些断点:
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.


一些注意事项:
保留您正在使用的.dat文件的备份。如果中止应用程序,文件经常会因为损坏而损坏(如@blabb所示),请将数据写回到文件中。 .dat文件似乎也处于活动状态,因此新下载的文件将导致不同的数据。

评论


一个问题:您是如何创建这些C代码片段的?手工还是使用一些工具?

– Harriv
13年7月24日在18:47

@Harriv:用手。一种方法,当例程时间不长时,可能是为每个寄存器(使用中的寄存器等)创建一个变量,并几乎用C绘制一个蓝图,然后您将看到什么并可以从中进行功能和循环。一段时间后,通过锻炼,第一次变得更容易识别什么是什么以及如何写得更紧凑……

– Runium
13年7月30日在15:18

#2 楼

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

每次连续读取均访问4000字节
完成267次ReadFile
使用例程(自定义??)对4000个字节中的每个字节进行异或运算,然后检查求和后的值,从而通过hexeditor打开死的dat文件dwords 0x3ffc,0x7ffc ......包含最新的校验和

您可能必须对xorring例程和校验和例程进行逆向工程

分析日志中的所有调用堆栈由windbg生成的具有此断点的文件

for (int i = 0; i< 0x267 ;i++) { checksum = *(DWORD *) (FilePointer)(0x3ffc + i) }

评论


万分感谢。我只是认为它被压缩了。

– Harriv
13年6月16日在7:37

#3 楼

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

#4 楼

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