leag/tbx
GitHub: leag/tbx
tbx 是一个 Python 实现的字节级精确反编译器,能从 Borland Turbo Basic 1.0/1.1 编译的 MS-DOS EXE 文件中完整恢复源代码。
Stars: 1 | Forks: 0
# tbx — 针对 Borland Turbo Basic 1.0/1.1 的字节级精确反编译器
`tbx` 能够从 Borland
Turbo Basic 1.0 或 1.1 编译的 16 位 MS-DOS EXE 文件中恢复 Turbo Basic 源代码。其正确性标准对于反编译器而言非常独特:
只有在**使用原始 Borland 工具链重新编译生成的源代码,并能够逐字节复现输入 EXE 文件**时,才算作一次成功的恢复。
```
tbx PROGRAM.EXE # recovered source on stdout
tbx PROGRAM.EXE -o PROGRAM.BAS # write to a file
tbx PROGRAM.EXE --ops # canonical op-stream dump (debugging)
```
Python API:
```
from tbx import decode0, emit0
source = emit0.emit(decode0.decode_user_code(open("PROGRAM.EXE", "rb").read()))
```
## 安装说明
要求 Python 3.11+。核心反编译器没有运行时依赖。
```
pip install .
```
基于反汇编的调试工具(见下文)需要
[iced-x86](https://pypi.org/project/iced-x86/),可以通过 `debug`
扩展获取:
```
pip install '.[debug]'
```
或者用于开发环境(配合 [uv](https://docs.astral.sh/uv/)):
```
uv sync
uv run pytest
```
## 运行原理
Turbo Basic 在运行时库之上编译为一种轻量级的线程化风格:浮点
运算使用 x87 *仿真* 编码(用 INT 34h+n 代替 ESC 操作码),
语句通过 INT ECh/EDh/EEh 子向量进行分发,控制流则是
与 INT 指令流交错的原始 x86 代码。解码器会:
1. **扫描**用户代码区域,将其转化为规范的操作流(`decode0.scan`),
从程序序言中自动检测编译器版本,
并在扫描时将 TB 1.0 移位过的 INT 编号标准化为 1.1 的编号,因此
下游的所有处理都与具体方言无关。
2. **求解 DGROUP 数据布局**(`decode0.layout`)—— 标量槽、数组
槽记录、常量池和字符串空间 —— 这些均基于操作流的
内存证据以及镜像尾部的结构推导得出。
3. **提升**操作流为带有类型的语句/表达式 IR(`decode0.core`、
`decode0.handlers`、`tbx.ir`),从其编译后的形态中折叠出结构化的控制流
(FOR/WHILE/DO、块 IF、SELECT CASE、SUB/DEF FN)。
4. **生成**规范源代码(`emit0`):每行一条语句,编号依次为
10, 20, …,变量按首次使用的顺序重命名为 A, B, C… —— 除非
原始行号对字节有显著影响(错误陷阱行表、TRON
追踪钩子),在这种情况下,它们会被精确恢复。
## 覆盖范围
完整实现了 Turbo Basic 手册涵盖的所有功能 —— 包括各种语句、内置
函数、元语句(`$STACK`/`$SOUND`/`$EVENT`)、事件陷阱、错误
处理(包括已编译的错误行表)、图形/blit 操作,以及
TRON/TROFF 追踪区域 —— 此外还有它们已知的交互作用。支持并自动检测两种编译器
方言;测试固件语料库在 TB 1.0 和 1.1 之间配对了各种构造。
## 设计理念
- **高调报错。** 校准词汇表之外的任何内容都会引发异常,而不会去
猜测。只有在固件程序验证了某个字节模式并且字节级精确的往返测试通过后,
该字节模式才会被加入词汇表。
- **跨方言的规范 IR。** TB 1.0 和 1.1 存在系统性差异
(每个分发表的子移位、向量移位、少数确实
存在不同编码的内容 —— RESUME、RUN、DEF SEG=、blit 描述符压栈)。
`decode0` 会在扫描时将其标准化为 1.1 的编号。
- **已知的丢损点会被标准化,而不是被猜测。** 编译器造成的难以区分的
别名会被渲染为一种重新编译后字节完全相同的规范形式
(STOP/SYSTEM ≡ END,INCR x ≡ x = x + 1,`PRESET (x,y),c`
≡ PSET-with-color,…)。由于 DATA 行的分组在物理上会被编译器
丢弃,因此 DATA 会重新生成为一条标准化的语句;原始行号
在具有字节级显著影响的地方会被精确恢复,在其他情况下则
可以自由重新编号。
## 测试
```
pytest
```
测试套件解码了数百个已编译的固件 EXE 文件
(`tests/fixtures/corpus/`),并检查它们的操作流和生成的源代码
与已提交的黄金参考文件(`tests/fixtures/ops/`、
`tests/fixtures/usercode/`)是否一致。每个固件在添加时都已
针对真实的 Turbo Basic 1.0/1.1 编译器完成了字节级精确验证;
以端到端的方式验证新的恢复结果需要访问原始的 DOS 工具链
(例如在模拟器下运行),而本代码库不包含也
不会自动化执行这些工具。
## 调试工具
解码器在设计上会高调报错:如果遇到无法识别的编译器模板,会抛出
包含出错字节和文件偏移量的 `ValueError`。这些工具的存在是为了
对这些错误进行分类排查,并用于维护黄金固件 —— 它们都不属于
反编译流水线的一部分:
- `tbx PROGRAM.EXE --ops` —— 转储规范的操作流而不是源代码,
以便查看语句扫描进展到了哪一步以及它识别出了什么。
- `python -m tbx.tools.cfgview PROGRAM.EXE [--out cfg.dot]` —— 将
用户代码区域作为原始 x86 进行反汇编
(`tbx/tools/insns.py`,通过 `debug` 扩展中的 iced-x86 实现),
并输出 Graphviz CFG。这是用于
检查 `unhandled byte ... at ...` 错误周边字节的工具,
用于分析扫描器遗漏了哪个编译器模板。
- `python tbx/tools/dump_ops.py` 和 `python tbx/tools/dump_user_code.py` ——
在解码器发生预期更改后,从语料库的 EXE 文件中
重新生成位于 `tests/fixtures/ops/` 和
`tests/fixtures/usercode/` 下的黄金固件。
## 许可证
MIT —— 见 [LICENSE](LICENSE)。
标签:BASIC, DOS, Python, 云安全监控, 云资产清单, 反编译器, 无后门, 逆向工具, 逆向工程, 静态分析