
Config-driven Dart AOT snapshot analyzer that exports blutter-compatible symbols and structs for IDA, radare2 and Frida, and decompiles functions into dart-analyze-clean Dart without a Dart SDK.
Config-driven Dart AOT snapshot analyzer and debug-info exporter. No Dart SDK, never runs the target: locates the embedded snapshot inside Mach-O / ELF / PE, exports the same symbols and structs as blutter for IDA / radare2 / Frida (with four deliberate corrections where the reference it was ported from was wrong — see
src/export/mod.rs), and decompiles functions into Dart thatdart analyzeaccepts. Covers desktop and real-device mobile (compressed-pointer) builds.
Works on any Dart AOT artifact — Flutter release builds, dart compile exe, dart compile aot-snapshot (Dart 2.7+ cluster snapshots).
Self-contained & auto-detecting — all 26 SDK profiles plus 21 compressed-pointer variants are embedded; the Dart version is matched by snapshot hash and the variant (compressed-pointers, i.e. every mobile Flutter build) from the snapshot's own features string, with a structural-probe fallback for custom/Flutter-engine builds. Verified against real shipping apps, not just our own builds:
dart analyze-clean Dart (95.9% and 91.1% structured).dart analyze 0 errors.material_3_demo (5 107 lines) and
animations (2 108 lines): 15 796 and 11 102 functions, both 92.5% structured, both
dart analyze 0 errors. Because the source is known, these are checked against it:
98.8% and 100% of the public classes/mixins/enums declared in lib/ are recovered, 95.4% and
97.4% of its string literals appear in the output, and 18/18 and 21/23 source files map to a
recovered library. tests/app_truth.rs asserts those ratios (floor 0.90) so the chain cannot
silently rot.Decompiles to valid Dart — lift → CFG → structured emission, not a disassembly dump: loops,
if/else, break/continue, object-pool literals inlined at their load site, recovered field
names as attribution comments. Across 26 artifacts (291 files, 24 253 functions) the output
analyses with 0 dart analyze errors, and on real apps it holds up too — Lark 3.6.1 at 95.9%
structured and Weibo 2.19.6 at 91.1% (19 053 functions, 1.53 M statements), both error-free.
Irreducible control flow keeps an explicit gotoLabel and a NOTE header rather than being
silently flattened. See Decompiler.
Fast — all measured with this release's binary: a 9 MB macOS Flutter sample exports in
0.26 s; --decompile takes 1.5 s on Lark (25.6 MB Android, 25 183 functions), 1.2 s on
material_3_demo (14 MB macOS, 15 796 functions) and 1.7 s on Weibo (9 MB Android,
19 053 decompiled functions / 1.63 M statements), at 172–263 MB peak RSS. That is ~89× faster
than v0.1.7 on the same artifact (106.8 s → 1.20 s) — from not rebuilding run-invariant data
per function, from streaming artifacts to disk instead of buffering them, and from rendering the
505 libraries in parallel; not from a faster algorithm. The parallel path is byte-identical to
the serial one (diff -rq over 1011 files), because file names and "which library emits each
entry point" are both settled by a sequential pre-pass before any rendering starts.
Bilingual CLI — Chinese locale prints Chinese, everything else English; override with DAE_LANG=zh|en.
Parallel decompiler — the 505-ish libraries are rendered concurrently (default n_threads(),
i.e. core count capped at 8); override with DAE_DEC_THREADS=N. Output is byte-identical at any
setting, because file names and "which library emits each entry point" are settled by a sequential
pre-pass first. Past 8 threads it gets slower and uses more memory on a 6P+12E-core Mac, so the
cap is the sweet spot, not a limitation. DART_AOT_PROF=1 prints a per-phase breakdown — note its
percentages can exceed 100% because the phases are CPU time summed across threads.
Progressive mode — 20 subcommands to query the snapshot like a database (libs, classes,
functions, members, strings, findrefs, callers, callees, pp, objs, stubs, ...)
and then decompile exactly one thing (getclass / getmethod / getlib / decompile --app).
Queries answer in 15–30 ms on a small corpus and 43–311 ms on a 15,796-function app; a full
export of that app is 0.55 s, or ~1.4 s with --decompile (~1000 files). Every command's output
is pasteable into the next one.
See Progressive mode.
No toolchain required — one self-contained binary: no Dart SDK, no Flutter install, and the target is never executed, only parsed. Mach-O/ELF/PE parsing and all 47 profiles are built in.
| Way | Command |
|---|---|
| Homebrew (macOS) | brew install ejfkdev/tap/dae |
| cargo | cargo install dae-rs |
| Prebuilt | binary from Releases — Windows/macOS/Linux × x64/arm64 |
| Source | cargo build --release |
macOS prebuilt binaries are ad-hoc signed; if Gatekeeper blocks the first run: xattr -dr com.apple.quarantine dae.
(The crates.io package is dae-rs because dae was taken; the repository, library and binary all stay dae.)
dae <binary> <out_dir> # auto-detect the Dart version
dae export <binary> <out_dir> # same thing, explicit verb
dae <binary> <out_dir> --sdk-profile P.json # or force one
dae <binary> <out_dir> --app --decompile # app-side code only (drops dart: and package:flutter)
dae help # every subcommand, grouped
dae help findrefs # one command's options and output columns
dae decompile <binary> | less # whole app's pseudocode to stdout
$ dart compile exe demo.dart -o demo
$ dae demo out
SDK profile: dart/3.13.0 (version-hash match)
export done -> /absolute/path/to/out:
ida_script/ r2_script/ frida.js asm/
text/ pp.txt · objs.txt · strings.txt · libs.txt · classes.txt · functions.txt · arrays.txt · maps.txt