文件位于:http://www.bittilahde.fi/Tietokanta.dat(英语为Database.dat)
簿记程序在这里:http://www.pesistulokset.fi/Kirjaus504.exe
我发现的内容:
数据库文件的直方图是完全平坦的
数据库文件中没有我能识别的标头
.exe是用Delphi 4编译的
我可以用IDR找到一些数据结构,但找不到找出如何解压缩文件。
下一步可能是什么?
#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与
dd
,xxd -ps
,grep
,diff
等配合使用。启动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上放置一个断点,然后查找数据库操作函数以查看其如何提取内容。由于用户界面为芬兰语,因此无法在您发布的程序上尝试此操作。
评论
您尝试过binwalk吗?@bueno不,我是新来的人,所以我从未听说过。您是否对此进行了改编:code.google.com/p/binwalk
是的,它将搜寻文件以查找各种不同的文件类型。我认为它需要python和libmagic。这是好东西。
@bueno看起来很不错,但是在这种情况下,它并没有告诉我任何有用的信息。熵图给出了整个文件的平面1.0。
我添加了binwalk作为答案,因为它可以帮助其他有类似问题的人。有关熵图和压缩的一些其他信息可以在这里找到:devttys0.com/2013/06/…