Skip to main content

stars

Read more on the blog: https://swarm.ptsecurity.com/fork-bomb-for-flutter/

reFlutter reverse-engineers Flutter apps by swapping the app's engine for a prebuilt patched one (repack mode) or by instrumenting the app's own engine at runtime (attach mode). What you get:

  • Traffic interception — boringssl certificate verification patched to succeed unconditionally, so Burp (or any proxy) sees the traffic; bypasses several Flutter certificate-pinning implementations. No root or certificate installation needed on Android.
  • Dump mode — a dump.dart JSONL with every function's name, class, library, static-ness, parameter count and code offset into the Dart instructions image, ready for Frida hooking or IDA/Ghidra naming (scripts/dump2disasm.py).
  • Runtime routes where repacking can't reach: a runtime SSL bypass for engines that ship unstripped, and a runtime dumper for Shorebird apps (whose private Dart fork makes patched-engine builds impossible).
  • Manual engine patching via a crafted Dockerfile if you want your own changes.

Supported engines

  • Android: arm64, arm32, x64 (x64 assets: Flutter >= 3.41);
  • iOS: arm64;
  • Release and profile builds, Stable and Beta channels — coverage is keyed by snapshot hash in enginehash.csv;
  • Pre-monorepo-merge engines (<= 3.27) are built from the archived flutter/engine repo — the 3.24.x assets ship with the same pipeline.

Install

# Linux, Windows, MacOS
pip3 install reflutter

Quick start

$ reflutter main.apk

Please enter your Burp Suite IP: <input_ip>

SnapshotHash: 8ee4ef7a67df9845fba331734198a953
The resulting apk file: ./release.RE.apk
Please sign the apk file

Sign and align the APK — uber-apk-signer works well:

java -jar uber-apk-signer.jar --allowResign -a release.RE.apk

For an IPA, sign and install the result as usual; the tool prints a reminder.

Options:

  • -p, --patch-dump — dump mode: the repacked engine emits dump.dart (JSONL: classes/methods/offsets) on start, and the tool writes a ready frida.js next to the output instead of the proxy instructions. Shorebird apps cannot use -p (their private Dart fork's snapshots don't run in patched engines) — dump those at runtime with scripts/frida-dump.js instead (see Shorebird builds).
  • -n, --no-interact — never prompt for a Burp IP (implies 127.0.0.1); useful for old engines in CI.
  • -b <Snapshot_Hash>, --build-engine — engine build mode: print the engine commit for a snapshot hash and (when run inside an engine checkout) apply the reFlutter source patches. See Build Engine.

Traffic interception

Point the app at a proxy on the same network. Configure the Burp listener:

  • Add port: 8083, bind to All interfaces;
  • Request handling: Support invisible proxying = True.

Android — set the device proxy, then install the repacked APK:

adb -s <device> shell "settings put global http_proxy <proxy_ip:port>"

Optionally route everything through Burp with TunProxy.

iOS — install the signed IPA and configure Potatso (or any per-app proxy tool) to use your Burp listener.

⚠️ Proxy routing by era: engines up to and including Flutter 3.24.x (snapshot hash 80a49c7111088100a233b2ae788e1f48) carry a hardcoded proxy IP that gets patched in place. From 3.27.x on, the hardcoded IP is gone — configure the proxy directly on the device as shown above.

Dump mode

Run with -p, start the app, and pull the JSONL dump:

reflutter -p main.apk && java -jar uber-apk-signer.jar --allowResign -a release.RE.apk
adb shell "cat /data/data/<PACKAGE_NAME>/dump.dart" > dump.dart

On iOS the dump lands in the app container; the console log prints the exact path.

file contents (one JSON object per function)
{"method_name":"_handleRequest","offset":"0x00000000000a8740","library_url":"package:anyapp/api/client.dart","class_name":"ApiClient","is_static":"false","parameter_count":"2"}

Offsets are relative to the Dart instructions image start (_kDartIsolateSnapshotInstructions / _kDartSnapshotText), not the ELF base. Two ways to use them:

  • Frida — the auto-written frida.js resolves the instructions symbol automatically (newer Dart exports _kDartSnapshotText, older _kDartIsolateSnapshotInstructions) and hooks an offset you fill in:

    frida -U -f <package> -l frida.js
    

    Works across Frida 14–17; prefer recent frida-server (16.x servers predate Android 15+ and cannot inject there — use 17.x).

  • Static analysis — generate naming scripts for IDA and Ghidra:

    python3 scripts/dump2disasm.py dump.dart
    

Runtime routes (no repack)

frida-ssl.js disables TLS verification without repacking at all by patching boringssl inside the loaded libflutter.so:

frida -U -f <package> -l frida-ssl.js

It resolves the internal-linkage verification function from .symtab, so it needs an engine that still carries a symbol table: Shorebird engines (ship unstripped, ~150 MB) and debug/profile builds qualify. Stock release APKs ship stripped engines — the bucket's symbols.zip is a separate link whose addresses do not transfer — so for those use reFlutter repack mode, which swaps in a prebuilt engine with the bypass compiled in. arm, arm64 and x64 are handled per-ISA; other arches abort without writing anything. Requires frida-server 17.x on Android 15/16 (16.x kills system_server there; 17.9.9 verified on API 36).

Shorebird builds

Apps built with Shorebird use a patched Flutter engine whose snapshot hash differs from the vanilla release it is based on. reFlutter identifies these automatically (a hash found only in enginehash_sb.csv identifies a Shorebird app even when flutter_assets/shorebird.yaml is absent).

Traffic interception works like any other app — reflutter app.apk/ipa — but with no engine build behind it: Shorebird's own engine artifact is fetched from their public bucket, boringssl's certificate-chain verification is patched to succeed unconditionally (a pure-Python ELF/Mach-O walk — symbol table to file offset to a per-ISA patch — after which the ~150 MB of shipped symbols are dropped, since Android's linker only reads program headers), and the app is repacked with it.

Dump mode runs at runtime instead — our patched engines cannot run Shorebird snapshots (their code lives in patchable regions understood only by their private Dart fork's loader — verified empirically), but the engine Shorebird ships inside the APK is unstripped. scripts/frida-dump.js hooks the app's own libflutter.so at FunctionDeserializationCluster::PostLoad — the exact splice point of the engine patch — and replays its JSONL dump by reading raw Dart object layouts, deriving the instructions-image base at runtime:

frida -D <device> -f <package> -l scripts/frida-dump.js
adb pull /data/data/<package>/dump.dart .
python3 scripts/dump2disasm.py dump.dart

Requires an arm64 AOT app and the stock, unrepacked engine (repacked copies are stripped — the script fails loudly there). Offsets use the same instructions-image convention as engine dumps; names carry the raw Class@12345 form (strictly more information than the engine patch's scrubbed names); parameter_count is unreliable on Shorebird snapshots. Validated live: 6,957 functions, offsets arm64-spot-checked against the APK's instructions image, consumed downstream by dump2disasm.

Notes: Android requires the engine artifact to carry a symbol table — recent revisions (Aug 2025+) ship with one; some older revisions are stripped and fail loudly. iOS requires a dSYM from the same build; dSYM availability varies by engine revision. The resulting IPA must be re-signed, as the tool's output already instructs.

Profile and Debug builds

Profile apps carry the same snapshot hash as their release counterparts (verified across engines — the hash covers the VM sources, not the runtime mode), so they work with the regular release engine assets; scripts/gen_enginehash.py --profile re-verifies this as new engines ship.

Debug builds are genuinely different (JIT kernel snapshots, no AOT libapp.so, debug artifacts embed the engine commit rather than a snapshot hash) — there is nothing to repack, and nothing to dump. For traffic analysis use frida-ssl.js, which runs on any engine that keeps its symbol table, debug builds included.


Maintainers

Build Engine

Engines are built with scripts/local-release (macOS; builds the traffic and dump variants for iOS + Android arm64/arm/x64, verifies every patch landed, and uploads the release assets), keyed by the snapshot hashes in enginehash.csv. The engine commit for a snapshot hash resolves via reflutter -b <Snapshot_Hash>. Pre-monorepo-merge engines (<= 3.27) are fetched from the archived flutter/engine repo and built with the matching gclient layout — the era needs a handful of toolchain compatibility patches (dead mirror pins, a libcxx roll against newer macOS SDKs), all applied automatically by the build. Backfill past hashes with scripts/backfill '<hash> ...'.

Hash-list refresh

scripts/update-enginehash refreshes the three CSVs — run it manually whenever you want the lists current; it is incremental and idempotent (seconds when nothing changed). On an always-on machine you can install a weekly launchd agent instead:

scripts/install-enginehash-agent   # Mondays 04:23; removal command printed

Custom Build

Manual Flutter code patching is supported using Docker:

git clone https://github.com/Impact-I/reFlutter && cd reFlutter
docker build -t reflutter -f Dockerfile .

Run with:

docker run -it -v "$(pwd):/t" -e HASH_PATCH=<Snapshot_Hash> -e COMMIT=<Engine_commit> reflutter

Flags:

  • -e x64=0 / -e arm64=0 / -e arm=0: disable that arch's build
  • -e WAIT=300: time in seconds to modify source before build
  • -e HASH_PATCH: snapshot hash from enginehash.csv
  • -e COMMIT: engine commit hash

Metadata

Release files for reflutter 0.9.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reflutter 0.9.8
File Size Uploaded
reflutter-0.9.8.tar.gz 46.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for reflutter 0.9.8
File Interpreter ABI Platform
reflutter-0.9.8-py3-none-any.whl Python 3 none any Details

Total release size: 89.2 kB

Release files / reflutter-0.9.8.tar.gz

Download URL reflutter-0.9.8.tar.gz
Size 46.2 kB
Tags Source
SHA-256 checksum
How to use checksums
fec99019ce91a95ed9f4c860436e82240c39acd4f8d21a5b67a01f26bf326598
BLAKE2b-256 checksum
How to use checksums
250cf117490c2730ba5f6029406c8198740319e075ec8a556b200f32c12f8502
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.10.18

Release files / reflutter-0.9.8-py3-none-any.whl

Download URL reflutter-0.9.8-py3-none-any.whl
Size 43.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0f7bfb052f11976ce9abd9c29c27919f58c998ce6a6c0f9d0e481e3ef637a910
BLAKE2b-256 checksum
How to use checksums
286f8fa91d766cb16801baf3dcd9502fe00cac395b3182102acda63b6ca720a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.10.18

Release history Release notifications | RSS feed

This release

0.9.8 This release

2 release files

0.9.7

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

1 release file

0.8.0

2 release files

0.7.9

2 release files

0.7.8

2 release files

0.7.7

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

1 release file

0.7.1

1 release file

0.7.0

1 release file

0.6.9

1 release file

0.6.8

1 release file

0.6.7

1 release file

0.6.6

1 release file

0.6.5

1 release file

0.6.4

1 release file

0.6.3

1 release file

0.6.2

1 release file

0.6.1

1 release file

0.6.0

1 release file

0.5.9

1 release file

0.5.8

1 release file

0.5.7

1 release file

0.5.6

1 release file

0.5.5

1 release file

0.5.4

1 release file

0.5.3

1 release file

0.5.2

1 release file

0.5.1

1 release file

0.5.0

1 release file

0.4.9

1 release file

0.4.8

1 release file

0.4.7

1 release file

0.4.6

1 release file

0.4.5

1 release file

0.4.4

1 release file

0.4.3

1 release file

0.4.2

1 release file

0.4.1

1 release file

0.4.0

1 release file

0.3.9

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page