ZhangZhuoSJTU/tiny-dec

GitHub: ZhangZhuoSJTU/tiny-dec

一个面向教学的迷你反编译器,通过 19 个独立阶段将 RISC-V RV32I 二进制文件逐步还原为可读 C 代码,帮助学习者从零理解反编译原理。

Stars: 86 | Forks: 4

tiny-dec

通过从零开始构建反编译器来学习反编译。
19 个阶段。每个步骤都可检查。从原始的 RISC-V 字节到可读的 C 代码。
RV32I:最简单的真实 ISA,因此重点始终放在反编译上,而不是指令编码。

Live Demo Python RISC-V RV32I 19 stages 500 tests

tiny-dec walkthrough

## 快速入门 ### 安装 ``` # System 依赖项 (Ubuntu/Debian) sudo apt-get install -y clang lld llvm binutils build-essential python3 python3-pip # Python 依赖项 pip install poetry poetry install # 为测试构建 RV32I fixture binaries ./scripts/build_fixtures.sh ``` ### 反编译 ``` # 完全反编译为 C (默认) tiny-dec decompile ./binary.elf # 针对特定函数 tiny-dec decompile ./binary.elf --func parse_record # 在任何中间阶段停止并查看内部 tiny-dec decompile ./binary.elf --stage ssa --func main # 检查 binary 元数据 tiny-dec info ./binary.elf ``` 每个阶段都是窥探流水线的一个窗口: ``` loader → decode → pcode → disasm → ir → simplify → dataflow → ssa → calls → stack → memory → scalar_types → aggregate_types → variables → range → interproc → structuring → c_lowering → c ``` ### 测试 ``` poetry run pytest # all 500 tests poetry run ruff check . # lint poetry run mypy tiny_dec # type check ``` ## 示例输出 **输入原始的 RISC-V 指令。** 输出可读的 C 代码:
汇编 (RV32I) 反编译输出
``` ; parse_record addi x2, x2, -32 sw x1, 28(x2) sw x8, 24(x2) addi x8, x2, 32 sw x10, -12(x8) sw x11, -16(x8) addi x10, x0, 0 sw x10, -20(x8) sw x10, -24(x8) jal x0, 0x11154 lw x10, -24(x8) lw x11, -16(x8) bge x10, x11, 0x111b8 lw x10, -12(x8) lw x11, -24(x8) slli x11, x11, 3 add x10, x10, x11 lw x11, 0(x10) lw x10, -20(x8) add x10, x10, x11 sw x10, -20(x8) lw x10, -12(x8) lw x11, -24(x8) slli x11, x11, 3 add x10, x10, x11 lw x11, 4(x10) lw x10, -20(x8) add x10, x10, x11 sw x10, -20(x8) lw x10, -24(x8) addi x10, x10, 1 sw x10, -24(x8) jal x0, 0x11154 lw x10, -20(x8) lw x1, 28(x2) lw x8, 24(x2) addi x2, x2, 32 jalr x0, 0(x1) ``` ``` #include typedef struct agg_8 { int32_t field_0; int32_t field_4; } agg_8; static uint32_t main(void) { int32_t local_24_4; int32_t local_20_4; int32_t local_16_4; int32_t local_12_4; uint32_t call_ret; local_12_4 = 20; local_16_4 = 2; local_20_4 = 10; local_24_4 = 1; call_ret = parse_record(&local_24_4, 2); return call_ret; } static uint32_t parse_record( agg_8* arg_x10_4, int32_t arg_x11_4) { int32_t local_24_4; int32_t local_20_4; local_20_4 = 0; local_24_4 = 0; while (local_24_4
它能处理的远不止循环和结构体: ``` // Switch recovery from equality-ladder CFG patterns static int32_t dispatch(uint32_t arg_x10_4, uint32_t arg_x11_4) { uint32_t local_24_4; uint32_t local_12_4; local_24_4 = arg_x10_4; switch (arg_x10_4) { case 0: local_12_4 = arg_x11_4 + 1; break; case 1: local_12_4 = arg_x11_4 + 4; break; case 2: local_12_4 = arg_x11_4 << 1; break; case 3: local_12_4 = arg_x11_4 - 3; break; default: local_12_4 = -1; break; } return local_12_4; } ``` ``` // Interprocedural analysis with prototype inference static int32_t main(void) { int32_t local_16_4; int32_t local_12_4; local_12_4 = 7; local_16_4 = helper(local_12_4); return local_16_4 - 2; } static int32_t helper(int32_t arg_x10_4) { return (arg_x10_4 << 1) + arg_x10_4 + 1; } ``` ## 流水线 19 个阶段,分为三个阶段。每个阶段都会生成一个类型化的、确定性的工件,你可以使用 `--stage` 进行检查。这些阶段的划分是为了学习目的;生产环境的反编译器会将其中几个阶段合并为更少、耦合更紧密的遍历。 ### 阶段 1:前端(字节到 IR) | # | 阶段 | 作用 | |---|-------|-------------| | 00 | [**Loader**](tiny_dec/loader/) | 解析 ELF 结构,解析符号,查找 `main`。加载器被故意设计得很简单:它将所有 ELF 解析委托给 [pwntools](https://github.com/Gallopsled/pwntools),并使用简单的启发式方法来发现 main,因为二进制格式解析不是本项目的教学重点。 | | 01 | [**Decode**](tiny_dec/decode/) | 将 RV32I 指令字解码为结构化对象 | | 02 | [**P-code Lift**](tiny_dec/ir/) | 将每条指令提升为语义化的 p-code 操作 | | 03 | [**Disassembly**](tiny_dec/disasm/) | 递归遍历以构建基本块和 CFG | | 04 | [**IR Containers**](tiny_dec/ir/) | 将函数和程序包装成持久的类型化容器 | ### 阶段 2:分析 | # | 阶段 | 作用 | |---|-------|-------------| | 05 | [**Simplify**](tiny_dec/analysis/simplify/) | 常量折叠,恒等消除,临时变量转发 | | 06 | [**Dataflow**](tiny_dec/analysis/dataflow/) | 过程内前向常量传播 | | 07 | [**SSA**](tiny_dec/analysis/ssa/) | 支配边界的 SSA 及内存版本控制 | | 08 | [**Calls**](tiny_dec/analysis/calls/) | 调用分类,ABI 参数/返回值快照 | | 09 | [**Stack**](tiny_dec/analysis/stack/) | 帧布局恢复,栈槽识别 | | 10 | [**Memory**](tiny_dec/analysis/memory/) | 针对栈、全局和指针访问的分区恢复 | | 11 | [**Scalar Types**](tiny_dec/analysis/types/) | 类型推断:`bool`、`int`、`pointer`、`word` | | 12 | [**Aggregate Types**](tiny_dec/analysis/types/) | 从指针算术模式中恢复结构体布局 | | 13 | [**Variables**](tiny_dec/analysis/highvars/) | 将 SSA 值和内存槽分组为命名变量 | | 14 | [**Range**](tiny_dec/analysis/range/) | 区间分析和分支谓词细化 | | 15 | [**Interproc**](tiny_dec/analysis/interproc/) | 跨函数原型推断和摘要 | ### 阶段 3:后端(结构化与发射) | # | 阶段 | 作用 | |---|-------|-------------| | 16 | [**Structuring**](tiny_dec/structuring/) | 从 CFG 拓扑中恢复 `if` / `while` / `switch` | | 17 | [**C Lowering**](tiny_dec/c_emit/) | 降低为带有类型化声明和表达式的类 C IR | | 18 | [**C Render**](tiny_dec/pipeline/) | 发出具有优先级感知格式的可读 C 代码 | ## 项目布局 ``` tiny_dec/ ├── loader/ ELF loading and symbol resolution ├── decode/ RV32I instruction decoder ├── ir/ P-code semantics and IR containers ├── disasm/ Recursive disassembler and CFG builder ├── analysis/ │ ├── simplify/ Canonical IR cleanup │ ├── dataflow/ Constant propagation │ ├── ssa/ SSA construction │ ├── calls/ Call modeling and ABI facts │ ├── stack/ Stack frame recovery │ ├── memory/ Memory partitioning │ ├── types/ Scalar and aggregate type recovery │ ├── highvars/ Variable recovery │ ├── range/ Range and predicate analysis │ └── interproc/ Interprocedural summaries ├── structuring/ Control-flow structuring ├── c_emit/ C lowering and rendering ├── pipeline/ End-to-end decompilation driver └── cli.py Command-line interface tests/ ├── fixtures/ │ ├── src/ 13 C programs (loops, structs, calls, switches, ...) │ └── bin/ 39 cross-compiled RV32I ELF binaries └── posts/ └── post_00/ ... post_18/ One test suite per pipeline stage ``` ## 为什么这样构建? **显式优于取巧。** 每个中间表示都是一个具体的、类型化的 Python dataclass。没有隐藏状态,没有魔法。 **确定性输出。** 每个阶段都有一个文本渲染器,在给定相同输入的情况下产生相同的输出。这既是调试界面,也是测试预言机。 **一个阶段,一个任务。** 每个阶段恰好解决一个问题,并将定义明确的工件传递给下一阶段。你可以随时停止流水线并检查它生成的内容。 **保守分析。** 流水线从不凭空捏造信息。当证据不明确时,它会保留原始形式而不是猜测。输出中残留的 `raw<...>` 表达式如实反映了分析未能解析的内容。 **这是一个教学工具,而不是生产级反编译器。** 流水线被刻意划分为许多小的、独立的阶段,以便可以独立研究每个概念。现实世界中的反编译器(Ghidra、IDA、angr、Binary Ninja)会为了性能和精度而融合、重排或迭代这些遍历。我们用效率换取了清晰度:如果你能理解这里的每个阶段,你就会明白生产工具在底层是如何工作的。 ## 固件二进制文件 13 个 C 程序在三个优化级别下编译为 RV32I ELF(共 39 个二进制文件,经过严格的无压缩指令验证): | 变体 | 标志 | 用途 | |---------|-------|---------| | `*_O0_nopie` | `-O0 -fno-pie` | 未优化,可预测的栈布局 | | `*_O2_nopie` | `-O2 -fno-pie` | 已优化,测试寄存器分配恢复 | | `*_O2_pie` | `-O2 -fpie` | 位置无关,测试 GOT/PLT 处理 | 使用 `./scripts/build_fixtures.sh` 重新构建(需要带有 RISC-V 支持的 `clang`、`lld`、`llvm`)。 ## 许可证 [MIT](LICENSE)
标签:Clang, DNS解析, DNS重绑定攻击, LLVM, Python, RISC-V, RV32I, SSA, TLS抓取, 中间表示, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 代码分析, 凭证管理, 反编译器, 安全规则引擎, 开源项目, 指令集架构, 控制流分析, 教育工具, 无后门, 汇编, 编程教学, 编译原理, 软件安全, 逆向工具, 逆向工程, 静态分析