MCPcopy Index your code
hub / github.com/caverav/flutterdec

github.com/caverav/flutterdec @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
943 symbols 2,105 edges 73 files 0 documented · 0%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

flutterdec banner

flutterdec

CI Release

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, diff, startup, and symbol-reporting artifacts.

What It Is For

flutterdec is meant for people reversing Flutter apps who want a practical first pass that is more readable than raw disassembly, while still being easy to verify against lower-level artifacts.

If you are new to the project, the fastest way to think about it is:

  • info tells you what the target looks like
  • decompile gives you pseudocode plus reports
  • diff compares two builds
  • map-symbols improves naming when you have matched engine binaries

Supported Scope

  • Android Flutter AOT
  • ARM64
  • static analysis
  • input as APK or libapp.so

Get Started

If you just want to try the tool, start with one of these paths:

  • no install: nix run
  • persistent Nix install: nix profile install
  • persistent install: release binaries

Run Without Installing

Run directly from GitHub:

nix run github:caverav/flutterdec -- --help
nix run github:caverav/flutterdec -- info ./sample.apk --json
nix run github:caverav/flutterdec -- decompile ./sample.apk -o ./out

Run from a local checkout:

nix run . -- --help
nix run . -- info ./sample.apk --json
nix run . -- decompile ./sample.apk -o ./out

Install Persistently With Nix

Install from GitHub:

nix profile install github:caverav/flutterdec
flutterdec --help

Install from a local checkout:

nix profile install .
flutterdec --help

Update later:

nix profile upgrade flutterdec

Install A Release Binary

Current prerelease: 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 --help

macOS 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 --help

Other platforms and future tags:

Releases page

Other Ways To Run It

Install into the user Cargo bin:

nix develop -c cargo install --path crates/flutterdec-cli
~/.cargo/bin/flutterdec --help

Run from source without installing:

nix develop -c cargo run -p flutterdec-cli -- --help
nix develop -c cargo run -p flutterdec-cli -- info ./sample.apk --json
nix develop -c cargo run -p flutterdec-cli -- decompile ./sample.apk -o ./out

Build a local release binary:

nix develop -c cargo build -p flutterdec-cli --release
./target/release/flutterdec --help

First Use

If this is your first run, this is the shortest useful path.

  1. Inspect the target:
flutterdec info ./sample.apk --json

For APK inputs, info reports Android startup summary fields such as:

  • android_startup_present
  • android_startup_confidence
  • android_startup_entrypoint_count
  • android_startup_flutter_activity_count

If adapter metadata is available, info also reports package and compatibility signals such as:

  • app_package_counts_top
  • adapter_kind
  • adapter_snapshot_hash_match
  • compatibility_warnings

  • Install the adapter for the detected Dart hash:

flutterdec adapter install --dart-hash <HASH>
  1. Decompile:
flutterdec decompile ./sample.apk -o ./out
  1. Open the main outputs first:

  2. out/pseudocode/*.dartpseudo

  3. out/report.json
  4. out/quality.json

That is enough to start working on most APKs.

Common Commands

Inspect target metadata:

flutterdec info ./sample.apk --json

Install and list adapters:

flutterdec adapter install --dart-hash <HASH>
flutterdec adapter list

Decompile with the default app-focused scope:

flutterdec decompile ./sample.apk -o ./out

Emit asm and IR too:

flutterdec decompile ./sample.apk -o ./out --emit-asm --emit-ir

Compare two builds:

flutterdec diff --old ./old.apk --new ./new.apk -o ./out-diff --json

Generate Ghidra and IDA import scripts:

flutterdec decompile ./sample.apk -o ./out \
  --emit-ghidra-script \
  --emit-ida-script

How To Use It

Choose Function Scope

By default, decompile focuses app reversing with --function-scope app-unknown and excludes known Flutter/Dart framework internals.

Available scopes:

  • app-unknown (default): app (package:*) plus unknown ownership functions
  • app: only app (package:*) functions
  • all: app plus Flutter, Dart runtime, and framework internals

Include everything:

flutterdec decompile ./sample.apk -o ./out --function-scope all

Focus only specific Dart packages (repeatable):

flutterdec decompile ./sample.apk -o ./out \
  --function-scope app-unknown \
  --app-package my_app

If package names are unknown, inspect report.json under function_scope.app_package_counts_top. When --app-package is not provided, capped prioritization also uses manifest-derived package hints under function_scope.priority_package_hints to favor app-owned code.

Decompile One Specific Function

Target a specific function by id:

flutterdec decompile ./sample.apk -o ./out \
  --target id:42 \
  --emit-asm

Target by entry address:

flutterdec decompile ./sample.apk -o ./out \
  --target va:0x613468 \
  --emit-asm

--target accepts:

  • id:<N>
  • va:0x<ADDR>
  • 0x<ADDR>
  • <N>

If <N> is ambiguous, flutterdec requires explicit id: or va:. Selection details are emitted in report.json.target_selection.

Improve Naming With Engine Symbols

If you have a stripped/unstripped libflutter.so pair, generate a symbol map:

flutterdec map-symbols \
  --stripped ./libflutter.stripped.so \
  --unstripped ./libflutter.unstripped.so \
  -o ./out/symbol-map \
  --register-local-cache

Then use that in later decompile runs:

flutterdec decompile ./sample.apk -o ./out \
  --extra-symbol-elf ./libflutter.unstripped.so

If 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.

Compare Two Builds

flutterdec diff --old ./old.apk --new ./new.apk -o ./out-diff --json

diff_report.json includes added, removed, and common function summaries plus added_packages_top and removed_packages_top churn summaries.

What To Look At

If you want to... Start with... Why
Read recovered logic pseudocode/*.dartpseudo Best first pass for branches, loops, returns, and named callsites
Validate the decompiler asm/*.s and ir/*.json Lets you confirm control flow and pool-backed calls
Understand startup report.json.android_startup Shows manifest anchors, startup stages, and DartEntrypoint evidence
Check analysis health quality.json and report.json Shows coverage, compatibility, target selection, and symbol-ingestion diagnostics
Review version-to-version changes diff_report.json Shows recovered function churn and package-level change summaries

Analysis Profiles And Engine Options

decompile exposes analysis-engine profiles so you can trade detail for speed.

Default profile:

  • balanced (recommended)

Available profiles:

  • balanced: full semantic naming, hints, and reporting
  • light: lower-overhead analysis for faster large-scale runs

Example:

flutterdec decompile ./sample.apk -o ./out --analysis-profile light

Adapter backend selection:

  • --adapter-backend auto (default): try Blutter backend if configured, otherwise fall back to the internal adapter
  • --adapter-backend internal: force the internal adapter only
  • --adapter-backend blutter: require the Blutter backend and fail if unavailable
  • --require-snapshot-hash-match: fail when the adapter-reported snapshot hash does not match the loader snapshot hash

Blutter backend environment knobs:

  • FLUTTERDEC_BLUTTER_CMD: full command to launch Blutter, for example python3 /path/to/blutter.py
  • FLUTTERDEC_BLUTTER_PY: path to blutter.py when you want the current Python interpreter to run it

Nix integration:

  • nix develop provides flutterdec-blutter and auto-exports FLUTTERDEC_BLUTTER_CMD to that wrapper
  • you can also run the wrapper directly via nix run .#blutter-bridge -- --help

Per-feature 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

Output Layout

Main outputs under -o <OUT_DIR>:

  • pseudocode/*.dartpseudo
  • quality.json
  • report.json
  • diff_report.json (if flutterdec diff)
  • asm/*.s (if --emit-asm)
  • opcode-prefixed asm lines (if --emit-asm --emit-asm-opcodes)
  • ghidra_apply_symbols.py (if --emit-ghidra-script)
  • ida_apply_symbols.py (if --emit-ida-script)
  • ir/*.json (if --emit-ir)

report.json also includes:

  • compatibility for schema, hash, and manifest alignment diagnostics
  • android_manifest for manifest-derived launcher, deeplink, and activity signals
  • android_startup for APK bytecode startup evidence such as embedding calls, JNI bootstrap stages, and recovered DartEntrypoint callsites when present
  • android_startup.dart_entrypoints entries can carry function_name, library_uri, and app_bundle_path when those values are directly recoverable from APK bytecode or simple helper return propagation
  • android_startup.bootstrap_chain summarizes observed Android embedder startup stages per source method, including ownership, stage ordering, completeness, and missing steps
  • engine_symbol_ingestion for auto-loaded local engine symbol cache matches keyed by libflutter.so build id
  • bootflow_discovery entries tagged by source (adapter, manifest, apk_startup)

See The Pipeline

The goal of these examples is simple: show original public source first, then show what flutterdec recovers from the shipped APK.

Original 1: Android Startup Surface

hiVPN MainActivity source snippet

Original 2: App-Side Flutter / Dart Surface

hiVPN Flutter bridge source snippet

Compare 1: Startup Source -> Recovered Startup Path

Source app: hiVPN v1.0.0 released on October 29, 2025. MainActivity and the manifest launcher are public in the repository: - Source repo: https://github.com/Mr-Dark-debug/hivpn - Release APK: https://github.com/Mr-Dark-debug/hivpn/releases/tag/release

Original

The app enters Flutter from MainActivity.onCreate. The second source card shows the app-side Flutter bridge that exposes MethodChannel('com.example.vpn/VpnChannel') to Dart code.

Recovered 1: APK Startup Report

hiVPN startup report excerpt recovered by flutterdec

Recovered

flutterdec parsed the APK manifest, recovered com.example.hivpn.MainActivity as the launcher, and correlated the startup chain from MainActivity.onCreate into Flutter JNI bootstrap calls such as attachToNative and nativeAttach.

Compare 2: App Source Using Flutter APIs -> Recovered Named Selectors

Source app: ZedSecure v1.2.0 - Source repo: https://github.com/CluvexStudio/ZedSecure - Release APK: https://github.com/CluvexStudio/ZedSecure/releases/tag/v1.2.0

This is the comparison you asked for: public app source that uses Flutter APIs, then recovered APK artifacts where flutterdec keeps recognizable Dart and Flutter selector names instead of only anonymous control flow.

Original Source

Original ZedSecure UI source using BoxConstraints(minWidth: 50)

This source is ordinary app UI code. It builds a ping badge with BoxConstraints(minWidth: 50) and other Flutter widget APIs inside the shipped app.

Recovered 2: From The ZedSecure APK With flutterdec

↓ ARM64

At the machine-code layer, the APK still looks like indirect selector dispatch through pool-loaded metadata and call targets.

ARM64 snippet from the recovered ZedSecure function showing the minWidth selector pool load

↓ Function IR

<img src="https://github.com/caverav/flutterdec/raw/main/docs/assets/readme/zedsecure-minwidth-ir.svg" alt="IR summary

Core symbols most depended-on inside this repo

emit_pseudocode_with_pool_hints
called by 49
crates/flutterdec-decompiler/src/lib.rs
push_component
called by 36
crates/flutterdec-disasm-arm64/src/lib.rs
push_line
called by 31
crates/flutterdec-decompiler/src/lib.rs
emit_pseudocode
called by 24
crates/flutterdec-decompiler/src/lib.rs
apply_name_and_type_hints
called by 24
crates/flutterdec-decompiler/src/passes/naming.rs
disassemble_program
called by 19
crates/flutterdec-disasm-arm64/src/lib.rs
compact_lines
called by 19
crates/flutterdec-decompiler/src/passes/compaction.rs
operand_expr
called by 16
crates/flutterdec-decompiler/src/control_flow/expression_lift.rs

Shape

Function 687
Method 134
Class 106
Enum 16

Languages

Rust97%
Python3%
Kotlin1%

Modules by API surface

crates/flutterdec-core/src/pipeline/apk_startup.rs87 symbols
crates/flutterdec-disasm-arm64/src/lib.rs71 symbols
crates/flutterdec-core/src/pipeline/runners.rs57 symbols
crates/flutterdec-decompiler/src/tests/emit_and_helpers/readability_and_naming.rs53 symbols
crates/flutterdec-core/src/pipeline/runners/tests.rs52 symbols
crates/flutterdec-decompiler/src/tests/cfg_and_stack/call_and_loops.rs46 symbols
crates/flutterdec-cli/src/main.rs44 symbols
crates/flutterdec-core/src/pipeline/runners/manifest.rs43 symbols
crates/flutterdec-core/src/pipeline/runners/symbols.rs30 symbols
crates/flutterdec-decompiler/src/passes/structural_helpers/naming_support.rs27 symbols
adapters/python/adapter_template.py27 symbols
crates/flutterdec-decompiler/src/lib.rs24 symbols

For agents

$ claude mcp add flutterdec \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact