flutterdec is a static Flutter AOT decompiler research tool for Android ARM64 binaries.
It takes an APK (or libapp.so) and emits readable pseudo-Dart plus optional IR/ASM artifacts.
Recover readable behavior from Flutter AOT ARM64 binaries with enough semantic structure that reverse engineering decisions can be made from pseudocode and reports.
- robust semantic extraction from snapshots and metadata (libraries, classes, functions, selectors, pool semantics)
- stable reverse-engineering-oriented pseudocode output for Android ARM64 release builds
- version-aware adapter behavior that can be updated without rewriting core/decompiler logic
- perfect source reconstruction of original Dart code
- broad multi-arch support in the same maturity level (x86, iOS, JIT modes)
- dynamic runtime emulation as the default analysis path
- reverse engineers and security researchers
- Flutter internals researchers
- developers comparing stripped/unstripped engine builds
- Run with
nix run(recommended, no install):
nix run github:caverav/flutterdec -- --help
nix run github:caverav/flutterdec -- info ./sample.apk --jsonFrom this repository checkout:
nix run . -- --help- Install release binary (
v0.1.0-alpha.2):
Linux x64:
curl -fLO https://github.com/caverav/flutterdec/releases/download/v0.1.0-alpha.2/flutterdec-v0.1.0-alpha.2-Linux-X64.tar.gz
tar -xzf flutterdec-v0.1.0-alpha.2-Linux-X64.tar.gz
sudo install -m 0755 flutterdec /usr/local/bin/flutterdec
flutterdec --helpmacOS arm64:
curl -fLO https://github.com/caverav/flutterdec/releases/download/v0.1.0-alpha.2/flutterdec-v0.1.0-alpha.2-macOS-ARM64.tar.gz
tar -xzf flutterdec-v0.1.0-alpha.2-macOS-ARM64.tar.gz
sudo install -m 0755 flutterdec /usr/local/bin/flutterdec
flutterdec --helpOther platforms and future tags:
- Other options:
Install into user Cargo bin (requires Nix with flakes enabled):
nix develop -c cargo install --path crates/flutterdec-cli
~/.cargo/bin/flutterdec --helpRun from source without installing:
nix develop -c cargo run -p flutterdec-cli -- info ./sample.apk --jsonBuild local release binary:
nix develop -c cargo build -p flutterdec-cli --release
./target/release/flutterdec --help- Inspect target:
flutterdec info ./sample.apk --jsoninfo now includes detected app package candidates (app_package_counts_top) when adapter metadata is available.
For APK inputs it also reports Android startup summary fields: android_startup_present, android_startup_confidence, android_startup_entrypoint_count, and android_startup_flutter_activity_count.
- Install adapter for the detected Dart hash:
flutterdec adapter install --dart-hash <HASH>- Decompile:
flutterdec decompile ./sample.apk -o ./outBy default, decompile focuses app reversing (--function-scope app-unknown) and excludes known Flutter/Dart framework internals.
To include all functions (app + Flutter + Dart/runtime):
flutterdec decompile ./sample.apk -o ./out --function-scope allTo focus only specific Dart packages (repeatable):
flutterdec decompile ./sample.apk -o ./out \
--function-scope app-unknown \
--app-package my_appIf package names are unknown, inspect report.json at function_scope.app_package_counts_top.
When --app-package is not provided, capped prioritization also applies manifest-derived package hints (function_scope.priority_package_hints) to favor app-owned code (including normalized variants like localsend_app and localsend when applicable).
To target a single function for developer-focused decompile/disassembly:
flutterdec decompile ./sample.apk -o ./out \
--target va:0x613468 \
--emit-asm--target accepts id:<N>, va:0x<ADDR>, 0x<ADDR>, or <N> (auto id/address match).
If <N> is ambiguous, flutterdec asks for explicit id: or va:. Selection details are emitted in report.json.target_selection.
- Optional: improve call names with stripped/unstripped engine pair:
flutterdec map-symbols \
--stripped ./libflutter.stripped.so \
--unstripped ./libflutter.unstripped.so \
-o ./out/symbol-map \
--register-local-cache
flutterdec decompile ./sample.apk -o ./out \
--extra-symbol-elf ./libflutter.unstripped.soIf the cached engine build id matches the APK’s embedded libflutter.so, decompile auto-loads the cached symbol_target_summary.json and reports it under report.json.engine_symbol_ingestion.
- Optional: compare two builds by recovered function signatures:
flutterdec diff --old ./old.apk --new ./new.apk -o ./out-diff --jsondiff_report.json includes added/removed/common function counts plus added_packages_top and removed_packages_top summaries.
- Optional: emit import scripts for RE tools:
flutterdec decompile ./sample.apk -o ./out \
--emit-ghidra-script \
--emit-ida-scriptdecompile exposes analysis-engine profiles so you can trade detail for speed.
Default profile:
balanced(recommended)
Available profiles:
balanced: full semantic naming/hints/reportinglight: lower-overhead analysis for faster large-scale runs
Example:
flutterdec decompile ./sample.apk -o ./out --analysis-profile lightAdapter backend selection:
--adapter-backend auto(default): try Blutter backend if configured, otherwise fallback to internal adapter--adapter-backend internal: force internal snapshot-string adapter--adapter-backend blutter: require Blutter backend (fail if unavailable)--require-snapshot-hash-match: fail early when adapter-reported snapshot hash does not match loader snapshot hash
Blutter backend environment knobs:
FLUTTERDEC_BLUTTER_CMD: full command to launch Blutter (for examplepython3 /path/to/blutter.py)FLUTTERDEC_BLUTTER_PY: path toblutter.py(uses current Python interpreter)
Nix integration:
nix developnow providesflutterdec-blutterand auto-exportsFLUTTERDEC_BLUTTER_CMDto that wrapper.- You can also run the wrapper directly via
nix run .#blutter-bridge -- --help.
You can explicitly enable/disable individual engine toggles:
--with-canonical-model-symbols/--no-canonical-model-symbols--with-pool-value-hints/--no-pool-value-hints--with-pool-semantic-hints/--no-pool-semantic-hints--with-semantic-reporting/--no-semantic-reporting--with-bootflow-category-seeds/--no-bootflow-category-seeds--with-apk-startup-analysis/--no-apk-startup-analysis
Main outputs under -o <OUT_DIR>:
pseudocode/*.dartpseudoquality.jsonreport.jsondiff_report.json(ifflutterdec diff)asm/*.s(if--emit-asm)- opcode-prefixed asm lines (if
--emit-asm --emit-asm-opcodes) ghidra_apply_symbols.py(if--emit-ghidra-script; applies symbol names and pool-load comments)ida_apply_symbols.py(if--emit-ida-script; applies symbol names and pool-load comments in IDA)ir/*.json(if--emit-ir)
report.json also includes:
compatibilityfor schema/hash/manifest alignment diagnosticsandroid_manifestfor manifest-derived launcher/deeplink/activity signalsandroid_startupfor APK bytecode startup evidence such as embedding calls, JNI bootstrap stages, and recoveredDartEntrypointcallsites when present- recovered
android_startup.dart_entrypointsentries can now carryfunction_name,library_uri, andapp_bundle_pathwhen those values are directly recoverable from APK bytecode android_startup.bootstrap_chainsummarizes observed Android embedder startup stages per source method, including app-vs-framework ownership, ordered stages, completeness, and missing stepsengine_symbol_ingestionfor auto-loaded local engine symbol cache matches keyed bylibflutter.sobuild idbootflow_discoveryentries tagged bysource(adapter,manifest,apk_startup); APK-startup-backed entries may appear withtarget_va: nullwhen the startup signal is real but the Dart function is not yet mapped
- User guide: docs/user-guide.md
- CLI reference: docs/cli-reference.md
- Development guide: docs/development.md
- Architecture: docs/architecture.md
- Internals walkthrough: docs/how-it-works.md
- Research decisions: docs/research-decisions.md
- Contributing: CONTRIBUTING.md
- Context and project history: context.md
- Bug report: new bug issue
- Feature request: new feature issue
- Research finding: new research issue
