处理 x86 分割重叠

逆向工程 艾达 x86 分割
2021-06-28 09:22:14

我将我的项目加载到 IDA 7 并选择 8086,因为这个 rom 在高地址空间中是 512kb。我应用了 0x80000 的偏移量并且它正确加载。

然后我转到 0xFFFF0 并找到一个远跳转到 0xFDAF:0x0 的远跳转到 0xC000:0x0 并且在该段中有大量的 0xC000:0xNNN(很多偏移)。

创建和缩小段是一场噩梦,所以我在二进制转储中搜索并在每次远跳 (0xEA) 后提取 8 个十六进制值。然后我格式化并计算物理地址。

您可以在段列表中看到大部分段重叠(例如):

0xC000:0x0000 = 0x0C0000
0xC000:0x12DB = 0x0C12DB
0xC003:0xA908 = 0x0CA938
0xC003:0xC908 = 0x0CC938
0xC007:0x506A = 0x0C50DA
0xC013:0x0903 = 0x0C0A33

从运行代码来看,重叠段是无关紧要的,但 IDA 不能有重叠。我该如何遍历代码?

文件:
ROM.BIN
段列表


0x49cdb: ea08a903c0 = jmp 0xc003:a908

00049cb0: 8b36 4a90 8b16 2990 2bf2 5e73 0b8b deb3  .6J...).+.^s....
00049cc0: 208b f3b0 08a2 8b90 f7c6 2000 7503 e9ab   ......... .u...
00049cd0: fd8b c6e8 9200 240f 3c07 747e a08a 903c  ......$.<.t~...<
00049ce0: 0774 05b0 00a2 8a90 a08b 903c 0874 03e9  .t.........<.t..
00049cf0: 8afd b200 b115 e8d7 45b0 d7e8 820d 8b36  ........E......6
2个回答

所以,一个完整的答案值得写一系列博客文章,但我会尝试触及高点:

  1. 虽然您可以使用不同的 seg:base 对来引用相同的位置,但在实际代码中这种情况很少发生。代码段对它们的所有功能使用相同的基数,并且不与邻居相交。但是,它们有时不会在恰好 16 字节对齐的边界处开始或结束。此 ROM 中的一个示例:

    • 具有基数的段E0470E047:0008(线性 0xE0478)开始,到(线性 0xF03F4)结束E047:FF84
    • 以下段使用基数F03F,因此从F03F:0004(也是线性的 0xF03F4)开始
  2. 在代码段内,近跳转和调用通常留在段内,并且函数不应超出段的末尾。这是确定段的真实边界的方法之一。例如,对于二进制文件中的基本 E047,我们有:

    • 调用到E047:0008,所以这可能是该段的开始。
    • far 调用E047:FF53可能是该段的最后一个函数。通过反汇编函数并仅跟随附近的跳转,我们来到了retf0xF03F2,或者E047:FF82,看起来该段在那里结束,我们可以在它之后添加额外的零字节,它可能是由链接器添加的用于填充,到达在E047:FF84(线性 0xF03F4)的最终边界处

更扩展的例子:在初步猜测段的C84B边界为C84B:0004to 后C84B:F112,我注意到问题列表中的这些说明:

SEG_C84B:96A2 call    0F2D5h
SEG_C84B:EF69 jmp     0F395h
SEG_C84B:F0C8 call    0F3DFh

由于这些是接近呼叫和跳跃,它们应该属于同一段。跟随它们为我们提供了段的扩展端为 0xd7894 或 C84B:F3E4。

  1. 作为上述的扩展,使用的数据引用cs应该指向段内的数据,并且在上下文中应该是有意义的。这在类似于以下内容的 switch 语句中最为明显:
2E FF A7 B6 59  jmp     cs:off_B000_59B6[bx]
2E FF A4 8E 76  jmp     cs:off_C000_768E[si]

一个很好的例子是 0xf05f1 处的这个:

2E FF A7 06 02                 jmp     cs:206h[bx]
68 01                          dw 168h
6E 01                          dw 16Eh
74 01                          dw 174h
80 01                          dw 180h
86 01                          dw 186h

如果我们假设跳转后的单词是跳转表,那么段应该0206h更早一些字节开始,并且确实有一个函数从 0xf03f4 开始,所以如果我们从那里开始一个段(以 0xf03f 为基数),我们的开关是很好地恢复:

SEG_F03F:01FD D1 E0          shl     ax, 1
SEG_F03F:01FF 8B D8          mov     bx, ax
SEG_F03F:0201 2E FF A7 06 02 jmp     cs:off_F03F_206[bx]
SEG_F03F:0206 68 01          dw offset loc_F03F_168  ; DATA XREF: sub_F03F_100+101↑r
SEG_F03F:0208 6E 01          dw offset loc_F03F_16E
SEG_F03F:020A 74 01          dw offset loc_F03F_174
SEG_F03F:020C 80 01          dw offset loc_F03F_180
SEG_F03F:020E 86 01          dw offset loc_F03F_186
SEG_F03F:0210 95 01          dw offset loc_F03F_195

我还没有完整的图片,但以下界限似乎很接近:

B000: 0xB0000-0xC0000 (full 64K)
C000: 0xC0000-0xC84B4
C84B: 0xC84B4-0xD7A52 (there is some weird stuff near the end)
E047: 0xE0478-0xF03F4
F03F: 0xF03F4-0xFDAF0
FDAF: 0xFDAF0-0xFFFF0

我在gist 中添加了一些我曾经使用过的脚本

注意:扫描仪打印出来的一些地址是误报,并不代表真正的段。您应该只考虑具有多个匹配项的那些。

较低段中的数据似乎被压缩了。

伊戈尔斯克在那里给出了很好的答复

这篇文章只是为了说明如何使用一些独立的反汇编器框架来寻找合适的 ljmps 而不是 xxd | tr 黑客

我在下面使用顶点

PS F:\zzzz> Get-Content .\rombi.py
from capstone import *

md = Cs(CS_ARCH_X86, CS_MODE_16)
fin = open("f:\\zzzz\\rom.bin" , "rb")
buf = fin.read()
fin.close()
fsiz =  len(buf)
offset = 0
while (offset < fsiz):
        offset  =  buf.find(b"\xea",offset)
        if(offset != -1):
                patt = buf[offset:offset+5]
                for i in md.disasm( patt ,(0x80000+offset)):
                        print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
                offset = offset + 1
        else:
                break

运行和分析结果

PS F:\zzzz> $foo = python.exe .\rombi.py

PS F:\zzzz> $foo.Count
484

last three far jumps
PS F:\zzzz> $foo[($foo.Count - 4)..$foo.Count]
0xfd7ce:        ljmp    0x2d20:0x4f00
0xfd7e9:        ljmp    0x2d20:0x5800
0xfdaf8:        ljmp    0xc000:0
0xffff1:        ljmp    0xfdaf:0

first three far jumps
PS F:\zzzz> $foo[0..3]
0x80162:        ljmp    0x507a:0xf0b8
0x8019c:        ljmp    0x4b48:0x4a6b
0x801fa:        ljmp    0xdae2:0xaa33
0x803c0:        ljmp    0x9b74:0xaa4a
PS F:\zzzz>