在OllyDbg 中查看它似乎是一项繁重的任务。看起来像一个带有加密和(自定义?)压缩数据的自定义数据库。在这样的应用中通常就是这种情况。具有结构化数据的平面文件不属于该文件的一部分。
无论如何。作为初学者:
在尝试了一些通用压缩工具(如 7z 或 binwalk)(尚未测试)后,可以快速检查一下,可以使用来自 Sysinternals 的ProcMon。启动 ProcMon,然后启动您的应用程序并在 ProcMon 中的应用程序上设置过滤器。你很快就会发现:
简而言之,它读取不同大小的块,但对于主要数据处理,它读取 16384 字节的块。过程分步骤:
- 生成 256 个整数的种子图。(在应用程序启动时完成一次。)
- 循环:
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对于这部分有dd
,xxd -ps
,grep
,diff
等。
启动 OllyDbg,打开应用,定位CreateFile
并设置断点:
00401220 $-FF25 18825000 JMP DWORD PTR DS:[<&kernel32.CreateFileA>; kernel32.CreateFileA
按F9
直到文件名 (in EAX
) 是 .dat 文件。在 上设置/启用断点ReadFile
。F9
阅读完成后,开始逐步查看完成的内容。
看着它:
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 文件似乎也处于活动状态,因此新下载它会产生不同的数据。