cormacj/z80-smart-disassembler
GitHub: cormacj/z80-smart-disassembler
这是一款专为Z80处理器设计的智能反汇编与逆向工程工具,能够自动识别字符串和数据区域,并支持通过模板和标签系统生成高可读性的代码。
Stars: 13 | Forks: 0
# z80-smart-disassembler
# 简介
这是一个 Z80 反汇编/逆向工程工具,旨在减轻反汇编代码的繁琐工作。它会尝试识别并正确标记字符串和数据区域。
如果您需要更精细的调整,它支持使用模板文件来提供覆盖规则,或添加特殊标签。
我编写此工具是为了对 Amstrad CPC 的 ROM 进行逆向工程,其中包含的模板主要针对此目的,但它能够处理您提供的任何 Z80 代码,即使它不是 Amstrad 文件。
该项目的灵感来源于 V Communications 的 Sourcer(详情见 https://corexor.wordpress.com/2015/12/09/sourcer-and-windows-source/),我也曾使用过它。我喜欢它的简单以及“直接搞定”的态度。
我希望为 Z80 代码提供类似的东西,而这个项目正是为了实现这一目标。
- [z80-smart-disassembler](#z80-smart-disassembler)
- [用法](#usage)
- [解码选项](#decoding-options)
- [格式化选项](#formatting-options)
- [模板](#templates)
- [辅助脚本](#helper-scripts)
- [用法示例](#example-usage)
- [结果示例](#example-results)
- [已知问题](#known-issues)
- [待办事项](#todo)
- [依赖项](#dependencies)
# 用法
```
z80-disassembler.py - v0.80 - A Smart Z80 reverse assembler
Visit https://github.com/cormacj/z80-smart-disassembler for updates and to report issues
usage: z80-disassembler.py [-h] [-q] [-o OUTFILE] [-t TEMPLATEFILE] [--labels LABELS] [-s STRINGTERMINATOR] [-a {maxam,z88,z80asm,pyradev}] [--style {lst,asm}] [-l LOADADDRESS]
[-e ENDADDRESS] [--xref {off,on}] [--stayincode] [--labeltype {1,2}] [-c {0,1,2}] [--explain {0,1,2}]
filename
A Smart Z80 reverse assembler
options:
-h, --help show this help message and exit
-q Quiet mode - don't display progress bars.
Required arguments:
filename A Z80 binary file.
Recommended arguments, but optional:
-o OUTFILE Output file. If omitted, then disassembly will go to the screen.
-l LOADADDRESS, --load LOADADDRESS
Specify where in RAM the code loads
-e ENDADDRESS, --end ENDADDRESS
Specify an address to stop disassembling. See README.md for more details.
Formatting options:
-t TEMPLATEFILE Use a template file. This helps decode strings and allows for fine tuning disassembly. See README.md for more details
--labels LABELSFILE Use a label file. This file provides user-defined labels that may be external to the program. See README.md for more details
-s STRINGTERMINATOR string terminator value - defaults are [0, 13, 141] and printable characters+0x80. You can supply a number, or a single character. You can repeat this as many times
as needed.
-a {maxam,z88,z80asm,pyradev}, --assembler {maxam,z88,z80asm,pyradev}
Format the code for particular assemblers. The default is z88.
--style {lst,asm} asm produces a file that can be assembled. lst is a dump style output. The default is asm style.
--xref {off,on} Enable or disable cross references for labels
--stayincode Don't try to decode data after a RET/JP
--labeltype {1,2} 1: Uses short label names eg D_A123 or C_A345 2: Uses descriptive label names, eg data_A123 or code_A123
-c {0,1,2}, --comments {0,1,2}
0: No comments 1: Address 2: (Default) Address+hex and ascii dump
--explain {0,1,2} 0: (Default) No code explanations 1: Data references only 2: Everything
```
# 解码选项
`-o OUTFILE`
将反汇编结果写入 OUTFILE。如果省略此选项,反汇编结果将输出到屏幕。
`-l LOADADDRESS`,或 `--load LOADADDRESS`
指定代码加载到 RAM 中的位置。如果程序被编写为加载到地址 0x100,则使用 `-l 0x100`,这样带有地址的调用和其他指令就能被正确解码。
`--end ENDADDRESS`
指定停止反汇编的地址。详情请参见 README.md。
从 .dsk 镜像中提取二进制文件意味着,一个 18 字节的二进制文件在提取后可能会变成 1024 字节。
例如,如果您有一个 18 字节的 .COM 文件,那么它的 --end 值应该是 `--end 0x118`,因为通常的加载地址是 0x100。
例如:
`./z80-disassembler.py --load 0x100 --end 0x118 EXAMPLE.COM`
# 格式化选项
`-s STRINGTERMINATOR 字符串终止符的值`
默认值为 [0, 13, 0x8d] 以及任何可打印字符+0x80。
根据需要多次重复此选项,例如 `-s 76 -s 0x81 -s ";"`
您可以提供一个数字,或单个字符,例如 `-s 0` 或 `-s "Q"`
`-a {pyradev,z80asm,maxam,z88}`
这会针对特定的汇编器应用特定的代码格式。默认是 z88。
Pyradev 要求十六进制地址格式为 `12cdH`
Maxam 使用 `&12cd` 格式的十六进制地址
z88 和 z80asm 使用 `0x12cd` 数字样式
z80asm 意味着 labeltype(标签类型)为 2,它使用更长的标签名(例如 `code_12CD`)。这是因为 z80asm 在处理较短的变量名时会存在问题。
`--style {lst,asm}`
默认是 asm 样式。
asm 会生成一个可以重新汇编的文件:
```
C_0108: ; XREF: 0x11C
LD A,(IX+0)
```
lst 是一种转储样式的输出:
```
C_0108: ; XREF: 0x11C
0x108: dd 7e 00 ".~." LD A,(IX+0) ;
```
`--xref {off,on}`
启用或禁用标签的交叉引用。这会在标签处添加一个 XREF 注释,其中包含调用此标签的地址。
`--stayincode`
不要尝试在 RET/JP 之后解码数据。有时反汇编器会假设 RET 或 JP 指令之后的数据是数据区,但实际上它是代码。这会强制反汇编器继续将后续字节视为代码处理,除非被模板指令覆盖。
`--labeltype {1,2}`
更改生成标签的格式。默认方法是短标签。
1:使用短名称,例如 D_A123 或 C_A345
2:使用全名,例如 data_A123 或 code_A123
`-c {0,1,2}, --comments {0,1,2}`
更改生成注释的显示方式。
0:无注释
```
LD A,(IX+0)
CP 0x1f
```
1:地址
```
LD A,(IX+0) ;0x108:
CP 0x1f ;0x10b:
```
2:(默认)地址+十六进制和 ASCII 转储
这是最有用的,因为它会显示指令的 ASCII 转储,因此更容易判断字符串是否被意外解码成了代码。
```
LD A,(IX+0) ;0x108: dd 7e 00 ".~."
CP 0x1f ;0x10b: fe 1f ".."
```
`--explain {0,1,2}`
0:(默认)无代码解释
```
LD A,0x2e ;0x11f: 3e 2e ">."
CALL 0xbb5a ;0x121: cd 5a bb ".Z."
```
1:仅数据引用
```
LD A,0x2e ;0x11f: 3e 2e ">." Load A with 0x2e
CALL 0xbb5a ;0x121: cd 5a bb ".Z."
```
2:所有内容
```
LD A,0x2e ;0x11f: 3e 2e ">." Load A with 0x2e
CALL 0xbb5a ;0x121: cd 5a bb ".Z." The current PC value plus three is pushed onto the stack, then PC is loaded with 0xbb5a.
```
# 标签文件
通过在命令行中添加 `--labels LABELSFILE` 来使用。标签文件允许定义和使用外部调用(例如 BIOS 入口点)到反汇编代码中。我在此仓库中包含了 `amstrad-labels.txt` 作为示例,以便您直接使用。
本仓库为 Amstrad CPC 提供了两个标签文件:`amstrad-labels.txt` 和 `amstrad-labels-commented.txt`。第一个只是 BIOS 调用列表,第二个是带有附加注释的 BIOS 调用列表。
您还可以在此文件中添加自定义代码标签,例如用于 RSX 跳转点的标签。
标签文件使用以下格式:
注释以 ';' 开头 - 如果一行以 `;` 开头,则该行将被忽略。空行也会被忽略。
添加在标签行末尾的注释将作为注释出现在输出中。可以使用 `\n` 将注释分割成多行,例如:
```
;A list of Amstrad CPC BIOS calls.
;Recorded here for use with the disassembler
KL_ROM_SELECT equ 0xb90f ; Select ROM slot\nEntry: C = ROM number\nExit: F: Z if no ROM present, NZ if present; carry clear if successful\nOn success: new ROM is paged; affects system jumpblock/firmware calls
KL_CURR_SELECTION equ 0xb912
KL_PROBE_ROM equ 0xb915
KL_ROM_DESELECT equ 0xb918
```
另一个例子,如果标签文件内容如下:
```
TXT_OUTPUT equ 0xbb5a ; Output character to screen\nEntry: C = ASCII char, HL = attribute (optional, or HL may be ignored)\nExit: C, HL preserved. Char output, cursor advanced.
```
输出将会是:
```
CALL TXT_OUTPUT ;0x120c: cd 5a bb ".Z."
; Output character to screen
; Entry: C = ASCII char, HL = attribute (optional, or HL may be ignored)
; Exit: C, HL preserved. Char output, cursor advanced.
```
# 模板
模板文件是一种向反汇编器指定如何处理特定内存区域的方式。此功能仍在开发中,某些功能尚未实现。
模板文件是一个标准的文本文件。文件格式如下:
* 注释以 ";" 开头
* 模板行的格式为:
`起始地址, 结束地址, 数据类型, 标签`
数据类型可以是以下之一:
b = 字节
w = 字
s = 字符串
c = 代码
p = 指针
您可以通过将地址括在 () 中来引用指针。当反汇编器看到此内容时,它会查看指针位置的 word(字)并使用该值。 例如,在 Amstrad ROM 中,0xc004 是指向命令名表的指针。如果 ROM 在位置 `0xc004` 处有一个值为 `0xc123`,则模板行应如下所示: `0xc006,(0xc004),c,JUMP_TABLE` 在反汇编器中,这将被视为标记从 `0xc006` 到 `0xc123` 的位置为代码,并且该区域的标签为 `JUMP_TABLE` # 辅助脚本 * generate_string_locations.sh **(仅限 linux)** **描述:** 反汇编器会尝试自动识别代码中的字符串,但有时会失败,因为它将字符串解码为了 JP 或 LD 指令,或者将代码当作了字符串。此辅助脚本通常能更成功地识别字符串,并生成可添加为模板文件的输出。 **用法:** `./generate_string_locations.sh <文件名> <内存加载位置>` **示例:** `./generate_string_locations.sh CPMFILE.COM 0x100 >cpmfile_template.txt` 此脚本将使用反汇编器的模板功能提前标记字符串区域。一旦这些区域被标记,反汇编器将忽略这些内存位置,假设人为指定比它的自动推断更准确。 此生成器可能会产生一些误报,因此我建议检查生成的模板,并注释掉(或删除)看起来不像字符串的任何内容。 输出格式如下: ``` ;---- ;db "Out of memory." 0x16a,0x178,s,S_16a ``` 在此示例中,`;----` 是用于提高可读性的分隔注释。 接下来是它可能生成的内容,在本例中为 `;db "Out of memory."` 接下来是模板行,使用格式 `起始地址, 结束地址,s 代表 string(字符串),标签`,因此反汇编器将 `0x16a` 和 `0x178` 之间的所有内容标记为字符串,并将此区域标记为 `S_16a` # 用法示例 此命令反汇编 RODOS219.ROM 文件并将输出存储在 rodos.asm 中 ``` $ ./z80-disassembler.py RODOS219.ROM -t amstrad_rom_template.txt -o rodos.asm -l 0xc000 -a z80asm --labels amstrad-labels.txt z80-disassembler.py - v0.80 - A Smart Z80 reverse assembler Visit https://github.com/cormacj/z80-smart-disassembler for updates and to report issues Writing code to rodos.asm Disassembling RODOS219.ROM: 16384 bytes Loading code: |██████████████████████████████████████████████████| 100.0% Complete Pass 1: Identify addressable areas Progress: |██████████████████████████████████████████████████| 100.0% Complete Pass 2: Search for strings Progress: |██████████████████████████████████████████████████| 100.0% Complete Pass 3: Build code structure Progress: |██████████████████████████████████████████████████| 100.0% Complete Pass 4: Validate labels Progress: |██████████████████████████████████████████████████| 100.0% Complete Pass 5: Produce final listing Progress: |██████████████████████████████████████████████████| 100.0% Complete rodos.asm created! Lines of code: 9238 Code Labels: 734 Data Labels: 57 ``` # 结果示例 我编写了一个简单的 "hello world" 文件,并在 Amstrad 上编译了它。 ``` org &100 bdos equ &0005 ; BDOS entry point start: ld c,9 ; BDOS function output string ld de,msg ; address of msg call bdos ret msg: db 'Hello, world!$' end ``` 然后我将 HELLO.COM 文件复制回了我的 PC。 首先,我使用 `./generate_string_locations.sh HELLO.COM 0x100` 运行了 generate_string_locations 脚本 Amstrad 上的 .COM 文件长 18 字节,但从 .dsk 镜像中复制出来时,它长达 1024 字节。生成字符串的脚本也添加了许多不需要的额外细节,因此我只使用了第一行。 我还通过在模板中强制指定,确保反汇编器将其余部分视为代码。 我的模板文件是: ``` 0x100,0x108,c,Hello ;---- ;db "Hello, world!" 0x109,0x117,s,S_109 ;---- ``` 现在我使用 `./z80-disassembler.py -l 0x100 -e 0x118 -t h.txt -a maxam hello2.asm` 反汇编了 HELLO.COM,结果如下: ``` org &100 ;-------------------------------------- Hello: ; LD C,9 ;&100: 0e 09 ".." LD DE,S_109 ;&102: 11 09 01 "..." - References: "Hello, world!$" CALL &5 ;&105: cd 05 00 "..." RET ;&108: c9 "." ;-------------------------------------- S_109: ; DEFB "Hello, world!$", &00 ;&109: &109 to &11a ``` # 已知问题 * 反汇编器可能会生成对不存在的标签的引用 * 某些标签已生成,但从未被调用。 * 字符串检测在接近 ROM 末尾或其他地方会出现奇怪的失败,如果发生这种情况,请使用 `generate_string_locations.sh` 辅助脚本制作模板。 * 某些使用 LDIR 移动的代码无法被正确解码,因为反汇编器不知道它是数据还是代码。推荐的解决方法是使用模板选项告诉反汇编器该怎么做,例如 `0xc300,0xc309,c,RELOCATE_BUILTIN_MSG`。这会导致反汇编器将 0xc300 和 0xc309 之间的数据视为代码,并分配标签 `RELOCATE_BUILTIN_MSG` # 待办事项 [ ] - 到处都需要错误处理 [ ] - 完善模板功能的实现(b, w 的处理) # 依赖项 我使用了来自 https://github.com/lwerdna/z80dis 的代码作为反汇编器引擎。 该代码已包含在此发行版中。
b = 字节
w = 字
s = 字符串
c = 代码
p = 指针
您可以通过将地址括在 () 中来引用指针。当反汇编器看到此内容时,它会查看指针位置的 word(字)并使用该值。 例如,在 Amstrad ROM 中,0xc004 是指向命令名表的指针。如果 ROM 在位置 `0xc004` 处有一个值为 `0xc123`,则模板行应如下所示: `0xc006,(0xc004),c,JUMP_TABLE` 在反汇编器中,这将被视为标记从 `0xc006` 到 `0xc123` 的位置为代码,并且该区域的标签为 `JUMP_TABLE` # 辅助脚本 * generate_string_locations.sh **(仅限 linux)** **描述:** 反汇编器会尝试自动识别代码中的字符串,但有时会失败,因为它将字符串解码为了 JP 或 LD 指令,或者将代码当作了字符串。此辅助脚本通常能更成功地识别字符串,并生成可添加为模板文件的输出。 **用法:** `./generate_string_locations.sh <文件名> <内存加载位置>` **示例:** `./generate_string_locations.sh CPMFILE.COM 0x100 >cpmfile_template.txt` 此脚本将使用反汇编器的模板功能提前标记字符串区域。一旦这些区域被标记,反汇编器将忽略这些内存位置,假设人为指定比它的自动推断更准确。 此生成器可能会产生一些误报,因此我建议检查生成的模板,并注释掉(或删除)看起来不像字符串的任何内容。 输出格式如下: ``` ;---- ;db "Out of memory." 0x16a,0x178,s,S_16a ``` 在此示例中,`;----` 是用于提高可读性的分隔注释。 接下来是它可能生成的内容,在本例中为 `;db "Out of memory."` 接下来是模板行,使用格式 `起始地址, 结束地址,s 代表 string(字符串),标签`,因此反汇编器将 `0x16a` 和 `0x178` 之间的所有内容标记为字符串,并将此区域标记为 `S_16a` # 用法示例 此命令反汇编 RODOS219.ROM 文件并将输出存储在 rodos.asm 中 ``` $ ./z80-disassembler.py RODOS219.ROM -t amstrad_rom_template.txt -o rodos.asm -l 0xc000 -a z80asm --labels amstrad-labels.txt z80-disassembler.py - v0.80 - A Smart Z80 reverse assembler Visit https://github.com/cormacj/z80-smart-disassembler for updates and to report issues Writing code to rodos.asm Disassembling RODOS219.ROM: 16384 bytes Loading code: |██████████████████████████████████████████████████| 100.0% Complete Pass 1: Identify addressable areas Progress: |██████████████████████████████████████████████████| 100.0% Complete Pass 2: Search for strings Progress: |██████████████████████████████████████████████████| 100.0% Complete Pass 3: Build code structure Progress: |██████████████████████████████████████████████████| 100.0% Complete Pass 4: Validate labels Progress: |██████████████████████████████████████████████████| 100.0% Complete Pass 5: Produce final listing Progress: |██████████████████████████████████████████████████| 100.0% Complete rodos.asm created! Lines of code: 9238 Code Labels: 734 Data Labels: 57 ``` # 结果示例 我编写了一个简单的 "hello world" 文件,并在 Amstrad 上编译了它。 ``` org &100 bdos equ &0005 ; BDOS entry point start: ld c,9 ; BDOS function output string ld de,msg ; address of msg call bdos ret msg: db 'Hello, world!$' end ``` 然后我将 HELLO.COM 文件复制回了我的 PC。 首先,我使用 `./generate_string_locations.sh HELLO.COM 0x100` 运行了 generate_string_locations 脚本 Amstrad 上的 .COM 文件长 18 字节,但从 .dsk 镜像中复制出来时,它长达 1024 字节。生成字符串的脚本也添加了许多不需要的额外细节,因此我只使用了第一行。 我还通过在模板中强制指定,确保反汇编器将其余部分视为代码。 我的模板文件是: ``` 0x100,0x108,c,Hello ;---- ;db "Hello, world!" 0x109,0x117,s,S_109 ;---- ``` 现在我使用 `./z80-disassembler.py -l 0x100 -e 0x118 -t h.txt -a maxam hello2.asm` 反汇编了 HELLO.COM,结果如下: ``` org &100 ;-------------------------------------- Hello: ; LD C,9 ;&100: 0e 09 ".." LD DE,S_109 ;&102: 11 09 01 "..." - References: "Hello, world!$" CALL &5 ;&105: cd 05 00 "..." RET ;&108: c9 "." ;-------------------------------------- S_109: ; DEFB "Hello, world!$", &00 ;&109: &109 to &11a ``` # 已知问题 * 反汇编器可能会生成对不存在的标签的引用 * 某些标签已生成,但从未被调用。 * 字符串检测在接近 ROM 末尾或其他地方会出现奇怪的失败,如果发生这种情况,请使用 `generate_string_locations.sh` 辅助脚本制作模板。 * 某些使用 LDIR 移动的代码无法被正确解码,因为反汇编器不知道它是数据还是代码。推荐的解决方法是使用模板选项告诉反汇编器该怎么做,例如 `0xc300,0xc309,c,RELOCATE_BUILTIN_MSG`。这会导致反汇编器将 0xc300 和 0xc309 之间的数据视为代码,并分配标签 `RELOCATE_BUILTIN_MSG` # 待办事项 [ ] - 到处都需要错误处理 [ ] - 完善模板功能的实现(b, w 的处理) # 依赖项 我使用了来自 https://github.com/lwerdna/z80dis 的代码作为反汇编器引擎。 该代码已包含在此发行版中。
标签:Amstrad CPC, Cutter, DAST, Python, ROM解析, Z80架构, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 代码分析, 凭证管理, 反汇编工具, 复古计算机, 字符串识别, 嵌入式安全, 快速连接, 恶意软件分析, 数据区识别, 无后门, 汇编语言, 源码还原, 自动化反汇编, 逆向工具, 逆向工程, 静态分析