今天我们还是继续 eve,顺带一提,我又切换回了 ghidra, 因为 ghidra 再一次向我证明了,它虽然 UI 很差,但是功能绝对够用。
本系列基本完结,以下是目录供快速翻阅:
每次用 cutter 的理由都很简单,UI 更好,更简洁。而 ghidra 简直就是个弹窗地狱, 查询的条件和结果都不能放在一起。所以每次我被 ghidra 恶心到的时候, 就想着“cutter 又更新了,看看会不会好一点”,然后用着用着,就又切回 ghidra, 这可能和「女生喜欢看帅的男生做任何事」的情结类似, 我也总会给颜值高的应用多一些机会,但是最终还是会选那个脚踏实地的。
这次切换,其实还是偏移量的问题,比如,在日志中的1427:0166
这行:
1427:00000137 call 00000166 ($+2c) E8 2C 00 EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F70 DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:1 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:00000166 cmp byte cs:[00DE],00 cs:[00DE]=8C14 2E 80 3E DE 00 00 EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F6E DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:1 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:0000016C jne 0000016F ($+1) (down) 75 01 EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F6E DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:0000016F test word cs:[00A4],4000 cs:[00A4]=CF00 2E F7 06 A4 00 00 40 EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F6E DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:00000176 jne 0000018C ($+14) (down) 75 14 EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F6E DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:0000018C call 000038AF ($+3720) E8 20 37 EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F6E DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:000038AF lds si,cs:[00CC] cs:[00CC]=0034 2E C5 36 CC 00 EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F6C DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:000038B4 mov ax,cs:[001F] cs:[001F]=22B2 2E A1 1F 00 EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000034 EDI:000000A0 EBP:00000F78 ESP:00000F6C DS:421A ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:000038B8 call 000038F4 ($+39) E8 39 00 EAX:000022B2 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000034 EDI:000000A0 EBP:00000F78 ESP:00000F6C DS:421A ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
在 cutter 中是这样:
0000:41e6 2e803ede0000 cmp byte cs:[0xde], 0 ; compare two operands
0000:41ec 7501 jne 0x41ef ; jump short if not equal/not zero (zf=0)
0000:41ee c3 ret ; return from subroutine. pop 4 bytes from esp and jump there.
0000:41ef 2ef706a4000040 test word cs:[0xa4], 0x4000 ; set eflags after comparing two registers (AF, CF, OF, PF, SF, ZF)
0000:41f6 7514 jne 0x420c ; jump short if not equal/not zero (zf=0)
0000:41f8 e8f33c call fcn.00007eee ; fcn.00007eee ; calls a subroutine, push eip into the stack (esp)
0000:41fb e88700 call fcn.00004285 ; fcn.00004285 ; calls a subroutine, push eip into the stack (esp)
我们可以看到,偏移量有很大的问题,我想大部分人可能和我一样, 没办法做到脑内十六进制偏移量转换,所以这个偏移量对我们来说就没什么参考意义了。
我们再看看 ghidra:
1427:0166 2e 80 CMP byte ptr CS :[LAB_101f_00dd+1 ],0x0
3e de
00 00
1427:016c 75 01 JNZ LAB_1427_016f
1427:016e c3 RET
1427:016f 2e f7 TEST word ptr CS :[LAB_101f_00a4 ],0x4000
06 a4
00 00 40
1427:0176 75 14 JNZ LAB_1427_018c
1427:0178 e8 f3 3c CALL FUN_1427_3e6e undefined FUN_1427_3e6e()
1427:017b e8 87 00 CALL FUN_1427_0205 undefined FUN_1427_0205()
和日志中是完全一致的,不过这是调教后的结果,调教前是 1408:0166
,
我给基础地址增加了一个偏移量,加一个偏移量没有很难,打开 memory map:
找到小房子的图标:
就可以调整偏移量了:
调整完会有关于书签的警告,我还没有弄清这些警告的危险性,我的建议是导入程序文件后,不做任何操作,就直接调整偏移量。
书接上文,回到第一部分的函数表。我们推测从 rogo.gdt 的 0x30
位置开始,就是图像内容了,程序在这里反复进行如下操作:
子过程一共有 19 个,分布在 0~255 的区间内,很多,但是基本上无外乎直接填色,从其他位置复制,跳行,等等,我想大部分读者可能对此兴趣不大,我就不展开了。其中有一个特殊过程 0xff
,这个过程什么也不做直接返回,换句话说,当步骤 1 读取到 0xff
时,循环终止。
但是如果我们观察一下日志,就会发现,其实文件内容只读了一点点:
1427:000038FB lodsb AC EAX:000022B2 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000034 EDI:000000B4 EBP:00000F78 ESP:00000F6A DS:421A ES:22B2 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:000038FB lodsb AC EAX:00002200 EBX:00003B34 ECX:00000000 EDX:0000434E ESI:00000036 EDI:00005064 EBP:00000F78 ESP:00000F6A DS:421A ES:22B2 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1 TF:0 VM:0 FLG:00007202 CR0:00000010
1427:000038FB lodsb AC EAX:00002200 EBX:00003B34 ECX:00000000 EDX:0000434E ESI:00000038 EDI:00005D34 EBP:00000F78 ESP:00000F6A DS:421A ES:22B2 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007206 CR0:00000010
1427:000038FB lodsb AC EAX:000022F8 EBX:00003B0C ECX:00000000 EDX:0000434E ESI:00000041 EDI:00005FB4 EBP:00000F78 ESP:00000F6A DS:421A ES:22B2 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:1 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:000038FB lodsb AC EAX:0000222C EBX:00003B77 ECX:00000000 EDX:0000434E ESI:00000044 EDI:00006144 EBP:00000F78 ESP:00000F6A DS:421A ES:22B2 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:000038FB lodsb AC EAX:0000220B EBX:00003B18 ECX:00000000 EDX:0000434E ESI:00000051 EDI:000064B4 EBP:00000F78 ESP:00000F6A DS:421A ES:22B2 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:1 PF:0 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:000038FB lodsb AC EAX:00002200 EBX:00003B34 ECX:00000000 EDX:0000434E ESI:00000052 EDI:00006874 EBP:00000F78 ESP:00000F6A DS:421A ES:22B2 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1 TF:0 VM:0 FLG:00007202 CR0:00000010
1427:000038FC mov bl,al 8A D8 EAX:000022FF EBX:00003B34 ECX:00000000 EDX:0000434E ESI:00000053 EDI:00006874 EBP:00000F78 ESP:00000F6A DS:421A ES:22B2 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1 TF:0 VM:0 FLG:00007202 CR0:00000010
第七次就终止了循环,显然还有我们忽略的东西,我们看下函数表这部分的外层:
void __cdecl16near FUN_1427_38af(void)
{
undefined2 uVar1;
uVar1 = (undefined2)_DAT_1000_00cc;
FUN_1427_38f4();
_DAT_1000_00cc = CONCAT22(_DAT_1000_00ce,uVar1);
uVar1 = (undefined2)_DAT_1000_00d0;
FUN_1427_38f4();
_DAT_1000_00d0 = CONCAT22(_DAT_1000_00d2,uVar1);
uVar1 = (undefined2)_DAT_1000_00d4;
FUN_1427_38f4();
_DAT_1000_00d4 = CONCAT22(_DAT_1000_00d6,uVar1);
uVar1 = (undefined2)_DAT_1000_00d8;
FUN_1427_38f4();
_DAT_1000_00d8 = CONCAT22(_DAT_1000_00da,uVar1);
return;
}
前面我们还原的函数表部分是 FUN_1427_38f4
,上层调用这个函数调了四次,不用仔细分析想必很多人也猜出来了,FUN_1427_38f4
每次只读取了一个平面,我们的读取过程还需要调整。四个平面读取完,我们看看每个平面的大小:
plane end reached, buffer: 0, index: 26560,
plane end reached, buffer: 1, index: 26560,
plane end reached, buffer: 2, index: 26560,
plane end reached, buffer: 3, index: 26560,
如果宽度是 640,那么对于每个平面就是 80 个字节一行,这样的话图像大小就是 640x332,332 对应的 16 进制是 0x014c
,这就对应到了文件头的 0x0c
位置:
这样看来,我们应该可以画一画了,什么,还不知道调色板,熟悉我的系列的读者都知道,调色板只是装饰,随意指定一下就好:
好像画了什么,但好像又什么都没画,不过篇幅已经不短了,我们先到这里吧
接下来我们要解决的问题,是搞清楚为什么我们画出来了上面这个东西。