二进制不是单个整体块。我弄清楚了结构,但花了一些错误的开始。
首先,我按照您的提示从偏移量 0x200 开始将二进制文件加载到 IDA 中。函数被反汇编好了,但数据引用大部分都关闭了。所以我查看了字符串,看看是否可能有一些字符串表的候选对象。这一堆看起来很有希望:
ROM:0030D8F3 aLanguageZoneAl:.ascii "LANGUAGE ZONE : ALL"
ROM:0030D8F3 .byte 0
ROM:0030D907 aLanguageZoneJp:.ascii "LANGUAGE ZONE : JP"
ROM:0030D907 .byte 0
ROM:0030D91A aLanguageZoneUs:.ascii "LANGUAGE ZONE : US"
ROM:0030D91A .byte 0
ROM:0030D92D aLanguageZoneEu:.ascii "LANGUAGE ZONE : EU"
ROM:0030D92D .byte 0
现在,正确的方法是编写一个脚本来扫描数据库中的 dword 数组,该数组的值与字符串地址具有相同的差异。然而,我很懒,所以我对自己说,“图像基数/移位值可能是 0x100 的倍数,所以地址的最后一个字节将是相同的,即我需要寻找一个类似的表”:
.. .. .. F3
.. .. .. 07
.. .. .. 1A
这可以通过使用模式“F3 ? ? ? 07 ? ? ? 1A”(不带引号;'?' 表示通配符字节)的二进制搜索轻松完成。只有一击:
ROM:001A8DC4 .long unk_35A0F3
ROM:001A8DC8 .long unk_35A107
ROM:001A8DCC .long unk_35A11A
因此,如果第一个字符串应该在 35A0F3,我们需要将二进制文件重新设置为 0x35A0F3-0x30D8F3 = 0x4C800 字节。这可以通过 Edit-Segment-Rebase 程序...,Shift delta 来完成。完成后,我们的表格会很好地显示出来,并且还排列了其他字符串:
ROM:001F55C4 .long aLanguageZoneAl # "LANGUAGE ZONE : ALL"
ROM:001F55C8 .long aLanguageZoneJp # "LANGUAGE ZONE : JP"
ROM:001F55CC .long aLanguageZoneUs # "LANGUAGE ZONE : US"
ROM:001F55D0 .long aLanguageZoneEu # "LANGUAGE ZONE : EU"
ROM:001F55D4 .long aLanguageZoneEg # "LANGUAGE ZONE : EG"
ROM:001F55D8 .long aLanguageZoneEe # "LANGUAGE ZONE : EE"
ROM:001F55DC .long aLanguageZoneE1 # "LANGUAGE ZONE : E1"
ROM:001F55E0 .long aLanguageZoneCh # "LANGUAGE ZONE : CH"
ROM:001F55E4 .long aLanguageZoneUn # "LANGUAGE ZONE : Unknown"
但是,这使得代码从 4C800 开始,并且图像标题中没有这样的值,这让我怀疑这不是全貌。所以我决定寻找解析标题的代码。一个好的开始是寻找魔法值(如标头签名)。
寻找 52 48 49 52 ("RHIR") 没有得到任何命中(字节交换版本也没有)。但是,我记得 ARCompact 使用一种特殊的方式来编码 32 位立即数(又名“长立即数”):它们以混合字节序存储(高位字后跟低位字)。搜索 49 52 52 48,我得到一击,备份找到函数开始后,我得到以下信息:
ROM:0018538E sub_18538E:
ROM:0018538E
ROM:0018538E var_4= -4
ROM:0018538E
ROM:0018538E st.a fp, [sp,var_4]
ROM:00185392 mov fp, sp
ROM:00185396 sub sp, sp, 4
ROM:00185398 mov r18, r0
ROM:0018539A mov r16, r1
ROM:0018539C sub r1, fp, 4
ROM:001853A0 mov r0, 6
ROM:001853A2 bl sub_6CC44
ROM:001853A6 ld r0, [fp,var_4]
ROM:001853AA cmp r0, r16
ROM:001853AC bhs loc_1853B2
ROM:001853AE ld r0, =0x1C00114
ROM:001853B0 b loc_18541E
ROM:001853B2 # ---------------------------
ROM:001853B2
ROM:001853B2 loc_1853B2:
ROM:001853B2 ld r0, [r18]
ROM:001853B6 mov r13, r18
ROM:001853B8 mov r17, r18
ROM:001853BC cmp r0, 0x52494852
ROM:001853C2 bne loc_1853D8
ROM:001853C4 ld r0, [r17,8]
ROM:001853C8 and r20, r0, 0xFFFF00
ROM:001853D0 bl sub_50814
ROM:001853D4 cmp r0, r20
ROM:001853D6 beq loc_1853DC
ROM:001853D8
ROM:001853D8 loc_1853D8:
ROM:001853D8 ld r0, =0x1C00111
ROM:001853DA b loc_18541E
ROM:001853DC # ---------------------------
ROM:001853DC
ROM:001853DC loc_1853DC:
ROM:001853DC add r13, r13, 0x10
ROM:001853DE mov r15, 0
ROM:001853E0
ROM:001853E0 loc_1853E0:
ROM:001853E0 ldb r0, [r17,6]
ROM:001853E4 cmp r15, r0
ROM:001853E6 bge loc_18541C
ROM:001853EA ldw r0, [r13]
ROM:001853EC mov r14, r13
ROM:001853EE cmp r0, 1
ROM:001853F0 bne loc_185416
ROM:001853F2 ldw r1, [r14,4]
ROM:001853F4 ldw r2, [r14,6]
ROM:001853F6 asl r1, r1, 9
ROM:001853F8 asl r2, r2, 9
ROM:001853FA mov r19, r1
ROM:001853FC add r0, r1, r18
ROM:00185400 st r2, [fp,var_4]
ROM:00185404 mov r1, r2
ROM:00185406 bl sub_24C9A4
ROM:0018540A ld r1, [r14,0xC]
ROM:0018540C cmp r0, r1
ROM:0018540E beq loc_185414
ROM:00185410 ld r0, =0x1C00111
ROM:00185412 b loc_18541E
ROM:00185414 # ---------------------------
ROM:00185414
ROM:00185414 loc_185414:
ROM:00185414 add r13, r13, 0x10
ROM:00185416
ROM:00185416 loc_185416:
ROM:00185416 add r15, r15, 1
ROM:00185418 sexw r15, r15
ROM:0018541A b loc_1853E0
ROM:0018541C # ---------------------------
ROM:0018541C
ROM:0018541C loc_18541C:
ROM:0018541C mov r0, 0
ROM:0018541E
ROM:0018541E loc_18541E:
ROM:0018541E
ROM:0018541E mov sp, fp
ROM:00185422 ld.ab fp, [sp,8+var_4]
ROM:00185426 b __ac_pop_13_to_20
ROM:00185426 # End of function sub_18538E
我们可以清楚地看到检查0x52494852(或“RHIR”),所以它似乎r18指向标题的开头。经过一些颠倒的标题结构似乎是这样的:
struct ImageHeader
{
/* 00 */ uint32_t Signature;
/* 04 */ uint8_t unk4[2];
/* 06 */ uint8_t nblocks;
/* 07 */ uint8_t unk7[1];
/* 08 */ uint32_t platform;
/* 0C */ uint32_t unkC;
};
后跟一个块数组:
struct ImageBlock
{
/* 00 */ uint16_t type;
/* 02 */ uint8_t pad2[2];
/* 04 */ uint16_t start;
/* 06 */ uint16_t size;
/* 08 */ uint32_t unk8;
/* 0C */ uint32_t checksum;
};
代码正在检查类型==1 的块,它使用它start<<9作为从头开始的偏移量和size<<9块的大小来计算校验和并与checksum字段进行比较。
我制作了一个快速脚本来解析和打印标题,这是输出:
unk4: 00001001
blocks: 7
platform: 00050600
unkC: 01A40001
block 0 type: 1
offset: 1 (200)
size: 14dc (29b800)
unk8: 00048000
checksum: A1AA045A
block 1 type: 1
offset: 14dd (29ba00)
size: 731 (e6200)
unk8: 002E8000
checksum: 917C608A
block 2 type: 0
offset: 1c0e (381c00)
size: 54c (a9800)
unk8: 00430000
checksum: EA1137CD
block 3 type: 1
offset: 215a (42b400)
size: 21 (4200)
unk8: 004DA000
checksum: 8AE264F9
block 4 type: 0
offset: 217b (42f600)
size: 2a6 (54c00)
unk8: 004DA000
checksum: 8606D33F
block 5 type: 0
offset: 2422 (484400)
size: 34 (6800)
unk8: 004DA000
checksum: 802A3C3B
block 6 type: 2
offset: b420 (1684000)
size: 4 (800)
unk8: 00000000
checksum: 00000000
所以看起来我们有这些块:
# type offset size unk8
------------------------------
0 1 200 29b800 00048000
1 1 29ba00 e6200 002E8000
2 0 381c00 a9800 00430000
3 1 42b400 4200 004DA000
4 0 42f600 54c00 004DA000
5 0 484400 6800 004DA000
6 2 1684000 800 00000000
unk8 看起来很像一个内存地址,事实上,如果我们检查文件中 30DAF3 处的字符串“LANGUAGE ZONE : ALL”,这对应于块 #1,并且与我们的开始(720F3)的偏移量相同来自假设内存地址 002E8000 的数据库 (0035A0F3) 中的字符串。根据表拆分加载的数据并将类型 1 的块移动到正确的地址后,我得到了这个布局:
CHUNK0 00048000 002E3800
CHUNK1 002E8000 003CE200
CHUNK3 004DA000 004DE200