# AOTopsy
[](https://github.com/BroNils/aotopsy/actions/workflows/ci.yml)
[](https://github.com/BroNils/aotopsy/releases)
[](LICENSE)
一个 Dart AOT 快照分析器。将 `libapp.so`——Flutter 发布版 APK 中编译后的 Dart 代码——转换为函数名、类布局、调用图、行为信号和可读的伪代码。无需 Dart VM,无需 SDK 编译,无需运行时回退。
> **分支说明:** AOTopsy 是 **unflutter** 的分支,最初由 **Anthony Zboralski** 开发。原始的 `zboralski/unflutter` 仓库已不可用(被作者移除);社区延续版本位于 [`KristijanZic/unflutter`](https://github.com/KristijanZic/unflutter)。原始快照解析器、集群反序列化器、ARM64 反汇编流水线以及 Ghidra/IDA 集成的所有功劳均归原作者所有。AOTopsy 在此基础上扩展了 x86_64 支持、原生反编译器、全程序类型推断、Frida 脚本生成以及全面的文档。
## 可恢复的内容
| 输出 | 说明 |
|--------|-----------|
| 函数名 | 每个编译后函数的原始 Dart 名称 |
| 类结构 | 字段名、字节偏移、继承链 |
| 调用图 | 直接(BL)和间接(BLR/分派)调用边及其来源 |
| 字符串引用 | 哪些函数从对象池加载哪些字符串字面量 |
| 行为信号 | 加密、网络、赌博、SIM、位置、WebView、区块链关键词分类 |
| 伪代码 | 来自 ARM64 或 x86_64 机器代码的架构中立反编译输出 |
| Dart 源码导出 | 重建的整项目模块化 `.dart` 文件,包含类、字段和方法 |
支持 **ARM64** 和 **x86_64**。覆盖 **Dart 2.10 至 3.13**(3.13.2 是当前稳定前沿)——每个已建模版本在两种架构上均可干净解析,参见 [COVERAGE.md](https://github.com/bronils/aotopsy/blob/main/COVERAGE.md)(93 个样本构建,0 失败)。
## 准确性与诚实性
AOTopsy 以真实基准进行衡量,而非空口断言。测试套件在每次变更时强制校验两项属性:
| 指标 | 值 | 含义 |
|--------|-------|---------------|
| **名称恢复一致率** | 在 44 个真实基准构建上总体 **90.2%**(最差区间 81.3%,≥ 0.81 门槛下限) | 恢复的函数名与每个构建自身的 ELF `.symtab`(外部真实基准)进行对比——`TestSymtabDifferential`。完整的逐构建记分板:[BENCHMARK.md](https://github.com/bronils/aotopsy/blob/main/BENCHMARK.md)(`make bench`)。 |
| **反编译器语法有效性** | **100%** 有效 Dart | 每个输出的伪代码函数均可作为 Dart 解析——`TestDecompileQualityCorpus`。 |
| **捏造率** | **0%** | §2 规则:绝不将猜测的名称、类型或调用目标作为事实输出。未知项如实呈现(`indirectCall`、`<unknown>`、`dynamic`)。 |
真实基准孪生样本是我们无法再分发的真实生产构建,因此这些差分门控在**本地**运行;公共 CI 验证构建、vet、`gofmt`、`staticcheck`、单元测试(乱序)以及跨平台矩阵的覆盖率下限。`samples/` 不可再分发且已被 gitignore,因此依赖样本的测试在那里跳过——但仅当语料库完全缺失时;语料库存在但缺少已注册样本则会失败,因为这两者含义相反。此处每个数字的测量方式(真实基准、指标定义、复现):[METHODOLOGY.md](https://github.com/bronils/aotopsy/blob/main/METHODOLOGY.md)。发布二进制验证及下方诚实范围参见 [SECURITY.md](https://github.com/bronils/aotopsy/blob/main/SECURITY.md)。
## 快速开始
```bash
make build
./aotopsy libapp.so # full pipeline
./aotopsy doctor libapp.so # quick diagnostic
./aotopsy export-dart --lib libapp.so --out ./lib # reconstruct entire Dart source project
./aotopsy _debug decompile-native --lib libapp.so --find MyClass # find and decompile a function
```
关于在真实快照上进行五分钟演练,请参阅 [DEMO.md](https://github.com/bronils/aotopsy/blob/main/DEMO.md);当你拥有原始 APK 且不知从何入手时,请参阅 `WORKFLOW.md` 了解分步方法论。
## 工作原理
AOTopsy 将 Dart AOT 快照视为一种确定性的二进制语法。在给定正确约束(ELF 结构、快照魔数、版本哈希、CID 表、簇编码)的情况下,每个字节都恰好只有一种正确解释。解析器不断施加约束,直到仅剩一种解释存活——没有启发式,没有猜测。
该流水线分阶段运行,每个阶段都是一个从字节到结构化数据的纯函数:
```mermaid
flowchart TD
A[libapp.so] --> B[ELF parse]
B --> C[snapshot region extraction]
C --> D[version detection]
D --> E[cluster alloc<br/>object census]
E --> F[cluster fill<br/>field values, names, strings]
F --> G[instructions table<br/>code ranges, stub boundaries]
G --> H{architecture?}
H -->|ARM64| I[ARM64 disassembly]
H -->|x86_64| J[x86_64 disassembly]
I --> K[CFG + call edges<br/>register provenance]
J --> K
K --> L[type inference<br/>BLR receiver type resolution]
L --> M[signal classification<br/>behavioral keyword matching]
M --> N[JSONL + HTML + DOT<br/>pseudocode output]
```
两个独立的后端共享相同的前半部分(从 ELF 到簇填充),然后按架构拆分:ARM64 使用 `internal/disasm`,x86_64 使用 `internal/disasm/x86.go`。反编译器(`internal/decompiler`)通过统一的 IR 处理这两种架构。
```mermaid
flowchart LR
subgraph "Shared front half"
A[elfx] --> B[snapshot]
B --> C[cluster]
end
subgraph "ARM64 backend"
C --> D1[disasm ARM64]
D1 --> E1[callgraph]
E1 --> F1[signal]
end
subgraph "x86_64 backend"
C --> D2[disasm x86_64]
D2 --> E2[callgraph]
E2 --> F2[signal]
end
subgraph "Decompiler (both archs)"
C --> G[decompiler IR]
G --> H[pseudocode]
end
```
## 与 Blutter 的比较
```mermaid
flowchart LR
subgraph Blutter
direction TB
B1[libapp.so] --> B2[Compile matching<br/>Dart SDK]
B2 --> B3[Embed Dart VM]
B3 --> B4[Deserialize via<br/>VM internal APIs]
B4 --> B5[Perfect fidelity]
end
subgraph AOTopsy
direction TB
A1[libapp.so] --> A2[Parse binary format<br/>directly]
A2 --> A3[No VM, no SDK]
A3 --> A4[Version-specific<br/>format modeling]
A4 --> A5[Portability + speed]
end
```
[Blutter](https://github.com/worawit/blutter) 嵌入 Dart VM,通过其自身的代码路径反序列化快照。保真度完美,但需要为每个目标版本编译匹配的 Dart SDK——而且它仅支持 ARM64,不支持静态 x86_64。AOTopsy 是唯一静态、版本无关的分析器,带有原生伪代码反编译器和已发布的基准真值准确率。
AOTopsy 直接解析二进制格式。无需 VM,无需 SDK。代价是:Dart 各版本之间的每一处格式变更都必须显式建模。没有运行时来自动处理。
## 命令
### 完整流水线
```bash
aotopsy libapp.so # disasm + call edges + signal + metadata (ARM64: + Ghidra/IDA)
aotopsy signal libapp.so # same, skip metadata
aotopsy libapp.so --graph # also build call graph DOT files
```
标志:`--out <dir>`(默认:`<basename>.aotopsy/`)、`--quiet`、`--strict`、`--max-steps <n>`、`--k <n>`(信号上下文深度,默认 2)、`--decompile`。
`--decompile` 会将每个函数的 Dart 伪代码写入 `<out>/dart/`。它默认关闭,因为它会使输出目录大约增大到三倍;每次不带该选项运行时都会提示这一点,因此该功能是可被发现的,而不仅仅是存在。
### 诊断
```bash
aotopsy doctor libapp.so # Dart version, pointer size, support status, build features
aotopsy find-libapp --apk app.apk # locate libapp.so inside an APK
```
### 全项目 Dart 源码导出
将所有类、字段、方法、getter、setter 和构造函数重建为按原始库 URI 映射的模块化 `.dart` 文件:
```bash
aotopsy export-dart --lib libapp.so --out ./reconstructed_lib/ # full project export
aotopsy export-dart --lib libapp.so --out ./lib/ --app-only # filter out core/flutter framework
aotopsy export-dart --lib libapp.so --out ./lib/ --filter Auth # targeted export by name
```
### 高级原生反编译器(ARM64 + x86_64)
直接从二进制机器指令生成干净、符合习惯的 Dart 代码:
- **结构化控制流**:`for-in` 迭代器、`while`、`for`、`try-catch-finally`,并精确界定 PC 范围。
- **Async/Await 线性化**:将 `_SuspendState` 状态机展开为线性的 `await future` 和 `await for`。
- **Lambda 内联**:在调用点直接合成箭头回调 `(item) => process(item)`。
- **类型格**:在 SSA 值之间传播具体的 Dart 类型(`String`、`int`、`UserModel`),无需运行实时 VM。
- **Dart 惯用法**:空感知(`?.`、`??`、`??=`)、级联(`..`)、Set/List/Map 字面量、字符串插值(`"${a}${b}"`)。
```bash
aotopsy _debug decompile-native --lib libapp.so --find MyClass # locate by name
aotopsy _debug decompile-native --lib libapp.so --func 0x1a92728 # one function at a VA
aotopsy _debug decompile-native --lib libapp.so --from-main --out out/ # reachability from app entry
aotopsy _debug decompile-native --lib libapp.so --all --filter MyClass # bulk, filtered
```
警告:在没有设置较小的 `--max` 的情况下使用 `--all`,在真实应用上可能需要约 64GB 内存。建议优先使用 `--find`/`--func`/`--from-main`。
### Ghidra / IDA(仅限 ARM64)
```bash
aotopsy ghidra libapp.so # headless Ghidra with metadata injection
aotopsy ida libapp.so # headless IDA via idalib
```
两者都会拒绝 x86_64 输入。对于 x86_64 伪代码,请使用 `decompile-native`。
### Frida 脚本生成
```bash
aotopsy _debug decompile-native --lib libapp.so --func 0x1a92728 --gen-frida --gen-frida-out hooks.js
frida -U -f com.example.app -l hooks.js --no-pause
```
完整指南请参阅 `FRIDA.md`。
### 交叉引用与追踪
```bash
aotopsy _debug strings --lib libapp.so --find "X-Signature" --xref # which function loads this string?
aotopsy _debug ffi-trace --lib libapp.so --filter MyClass # dart:ffi call sites
aotopsy _debug dispatch-table --lib libapp.so --filter MyClass # dispatch table entries
aotopsy _debug fingerprint --lib libapp.so # build-id and version markers
aotopsy _debug funcdiff --old old.so --new new.so # function set diff
aotopsy _debug symbolmap --stripped lib.so --unstripped debug.so # resolve stripped targets
```
### 语料库工具
```bash
aotopsy inventory --dir samples/ # catalog APKs
aotopsy parity --samples samples/ --out out/ # cross-version parse report
aotopsy _debug thr-audit -lib libapp.so -out thr.jsonl # THR access scan
```
## 输出产物
| 文件 | 内容 |
|------|----------|
| `functions.jsonl` | 每个函数的名称、地址、大小、所有者、参数数量 |
| `call_edges.jsonl` | 带有已解析目标和来源的 BL/BLR 边 |
| `classes.jsonl` | 每个类的字段名、偏移量、实例大小 |
| `string_refs.jsonl` | 来自对象池加载的字符串引用 |
| `dispatch_table.jsonl`| 推断的分发表接收者类型和目标映射 |
| `signal.html` | 带上下文图的行为信号报告 |
| `dart_meta.json` | 快照元数据、压缩指针标志、THR 布局 |
| `flutter_meta.json` | 用于 Ghidra/IDA 的统一元数据(仅 ARM64) |
| `aotopsy.sarif` | SARIF 2.1.0 安全发现报告(用于 GitHub Code Scanning) |
| `evidence.jsonl` | 统一的证据模型,包含每个调用点的置信度和来源 |
| `asm/*.txt` | 每个函数的带注释反汇编 |
| `asm/*.bin` | 每个函数的原始指令字节(两种架构) |
| `dart/*.dart` | 每个函数的反编译伪代码(使用 `--decompile`) |
| `cfg/*.dot` | 每个函数的 CFG(使用 `--graph`) |
## 包布局
```
cmd/aotopsy/ CLI entry point and command handlers
internal/
analysis/ Pipeline orchestration, snapshot loader, and analysis engines
sdk/ Dart VM ground-truth facts, register mappings, and predicates
vmtables/ Versioned THR field offsets, stub names, and stub orders
thraudit/ Thread-relative memory access audit and classification
arch/arm64/ Centralized ARM64 bitmask instruction decoders
arch/x86/ Centralized x86_64 decode primitives and register helpers
naming/ Central pool lookups, name resolution, and stub builders
elfx/ ELF validation and symbol extraction
snapshot/ Snapshot region extraction, version profiles
dartfmt/ Dart VM variable-length integer encoding
cluster/ Two-phase snapshot deserialization (alloc + fill)
disasm/ ARM64 + x86_64 decode, CFG, call-edge provenance
callgraph/ Call graph construction and DOT rendering
signal/ Behavioral string and malware signal classification
render/ HTML/DOT/SVG visualization
output/ JSONL and SARIF 2.1.0 serialization
decompiler/ Dart-AOT pseudocode decompiler (both architectures)
typetrack/ Whole-program type inference and receiver recovery
fingerprint/ Build-id and version marker identification
funcdiff/ Function-set diffing between builds
symbolmap/ Stripped-vs-unstripped symbol resolution
ffitrace/ Static dart:ffi call-site tracing
strxref/ String-to-function cross-referencing
strutil/ Dart syntax sanitization and metadata serialization
jsonutil/ Generic JSONL stream readers and writers
evidence/ Unified evidence model with confidence and provenance
frida/ Frida runtime hook and probe generation
cli/ ANSI color helpers for CLI output
tools/ Standalone utilities (THR table extractor and validator)
ghidra_scripts/ Ghidra integration (Python)
ida_scripts/ IDA integration (Python)
```
有关每个包的深入介绍,请参阅 `ARCHITECTURE.md`。
## 构建
需要 Go 1.25+。
```bash
make build # build ./aotopsy
make install # install to ~/.aotopsy/bin, plus the Ghidra/IDA integration
# assets into ~/.aotopsy/ghidra_scripts and ~/.aotopsy/ida_scripts
make test # run tests
make bench # regenerate BENCHMARK.md (needs local ground-truth twins)
make coverage # regenerate COVERAGE.md (needs local corpus samples)
make analyze # cross-check export-dart output against `dart analyze`
```
集成测试从 `internal/samplecorpus` 解析其输入,该目录会查找 `samples/` 目录;无需进行任何设置。`samples/` 已被 gitignore 忽略,因此没有该目录的检出会跳过这些测试——但如果检出*包含*语料库却缺少已注册的样本,则会失败,因为语料库漂移与语料库缺失意味着不同的问题。
公共 CI 在 linux/amd64、darwin/arm64 和 windows/amd64 上运行 `gofmt`、`staticcheck`、构建、vet 和乱序单元测试,另有一个带覆盖率下限的 race+coverage 作业。
## 发布与分支
AOTopsy 采用双分支模型:
| 分支 | 角色 |
|--------|------|
| `main` | **稳定版。** 每个提交都是经过 squash 合并、门禁验证的发布候选。带标签的发布(含预构建的跨平台二进制文件)从此分支切出。 |
| `develop` | **滚动 / 每日构建。** 日常小型提交、功能和研究的首选落地分支。合并之间可能不稳定。一旦门禁通过,便通过 squash 合并 PR 批量合入 `main`。 |
请针对 `develop` 分支贡献;仅当一批工作通过门禁验证后,才向 `main` 提交 PR。
适用于 Linux、macOS 和 Windows(amd64/arm64)的预构建二进制文件附在每个
[GitHub 发布](../../releases)中。AOTopsy 是纯 Go 项目,因此 `make build` 可以干净地为任何目标交叉编译。
每个发布归档还会在二进制文件旁附带 `ghidra_scripts/` 和 `ida_scripts/`,
这正是 `aotopsy ghidra` 和 `aotopsy ida` 所发现的布局——因此这些
命令可直接从解压后的下载内容运行,无需检出代码。
## 限制与范围
AOTopsy 明确说明它能恢复什么、不能恢复什么。有些限制属于工程
范围;另一些则是 **AOT 格式的硬性下限**——即 Dart 编译器在
release(PRODUCT)构建中移除的信息,已对照 SDK 源码验证。我们记录这些下限,而不是在其上编造。
**硬性 AOT 下限(已验证——不要指望这些会改善):**
- **实例字段名约 97–99% 缺失。** `Precompiler::DropFields` 仅在
`#if !defined(PRODUCT)` 下保留字段名;真实应用约有 233 个具名 `Field` 对象,而
合成字段约 16k 个。基于访问器的恢复(`get:`/`set:` 仍携带名称)可部分穿透
此限制(−11–22%,确定性,绝不猜测);其余部分确实已丢失。
- **局部变量和捕获变量名已丢失。** 渲染为 `local_*` / `tN`。
- **真正多态的派发目标无法静态解析。** 通过
派发表 / 动态 `Closure` 对象的 BLR 是 AOT 限制,而非分析缺口;这些
会如实渲染为 `indirectCall` / `dynamicCall`。参见 [METHODOLOGY.md](https://github.com/bronils/aotopsy/blob/main/METHODOLOGY.md#limits-of-this-method-honestly)。
**工程范围:**
- **仅支持 AOT。** 不支持 JIT。
- **Ghidra/IDA 反编译仅支持 ARM64。** x86_64 会被拒绝并给出明确错误——请改用 `decompile-native`(唯一的静态 x86_64 Flutter 反编译器)。
- **`--all` 反编译可能导致主机崩溃。** 真实的全尺寸应用在无限制的 `--all` 下需要约 64GB 内存。请使用定向模式(`--find`、`--func`、`--from-main`)或用 `--max` 限制。
- **虚拟派发对 `--from-main` 可达性不可见。** Widget 生命周期回调通过 Flutter 框架派发,而非直接调用指令。类触碰启发式可恢复部分,但属于过度近似。其余请使用 Frida。
- **每个 Dart 版本变更都必须建模。** 没有 VM 能自动处理格式变更。快照携带的是 git 派生的哈希,而非版本号,因此支持基于结构。目前建模:Dart 2.10 → 3.13。
- **真值孪生不可再分发**(真实生产构建),因此
名称恢复差分在本地运行,而非公共 CI。
## 许可证
BSD-3-Clause。CID 表、THR 字段偏移和桩名称派生自 [Dart SDK](https://github.com/dart-lang/sdk)(同样为 BSD-3-Clause)。参见 `LICENSE` 和 `NOTICE`。