| name | flutter |
| description | Flutter app reversing and SSL pinning bypass — reFlutter Dart-AOT patching, BoringSSL bypass, libapp.so static analysis in Ghidra/radare2, Dart snapshot dump, and iOS Flutter.framework notes. |
| allowed-tools | Bash Read Write |
| metadata | {"subdomain":"mobile","when_to_use":"flutter dart aot libapp.so libflutter reflutter boringssl ssl pinning bypass snapshot dump cross-platform jadx-useless tunproxy uber-apk-signer","tags":"flutter, dart, reflutter, ssl-pinning, reverse-engineering, libapp, boringssl","mitre_attack":"T1635, T1521.003, T1406"} |
Flutter App Reversing Playbook
Flutter compiles Dart AOT to a native shared library (libapp.so /
Flutter.framework). jadx / apktool show only the thin Java/ObjC
shell and are useless for app logic. Use this playbook when static
Android or iOS skills yield nothing but an empty Java wrapper.
Prerequisites
- APK / IPA obtained (see
mobile/android/SKILL.md Path 1 for APK pull).
- Tools:
reflutter (pip install reflutter), uber-apk-signer
(download JAR from GitHub releases), adb + device or emulator,
jadx (identification only), Burp Suite proxy.
- For iOS:
reFlutter also patches Flutter.framework; use
objection patchipa or direct framework patching workflow.
Step 1: Identify a Flutter App
unzip -o base.apk -d /tmp/apk-out/
ls /tmp/apk-out/lib/arm64-v8a/
jadx -d /tmp/apk-java /tmp/base.apk
strings /tmp/apk-out/lib/arm64-v8a/libflutter.so | grep -i "flutter"
strings /tmp/apk-out/lib/arm64-v8a/libapp.so | grep -E "https?://"
Key tell: libapp.so size > 5 MB with no dex business logic; the
kernel_blob.bin in flutter_assets/ indicates a debug/JIT build
(rare in production; can extract Dart directly with dart_vm).
Step 2: reFlutter Workflow — Patch BoringSSL Pinning
reFlutter patches the ssl_crypto_x509_session_verify_cert_chain
function in libflutter.so to always return true, bypassing all
BoringSSL-based certificate validation (covers both custom pinning
and system trust).
Flutter <= 3.23.x (hardcoded proxy IP)
reflutter base.apk
java -jar uber-apk-signer.jar --allowResign -a release.RE.apk -o /tmp/
adb install /tmp/release.RE-aligned-signed.apk
Flutter >= 3.24.0 (no hardcoded proxy IP — breaking change)
Starting Flutter 3.24.0 (August 2024), the hardcoded proxy IP was
removed from the patched BoringSSL stub. Traffic is no longer
redirected to a fixed IP. Two options:
Option A: Device-level proxy
reflutter base.apk
java -jar uber-apk-signer.jar --allowResign -a release.RE.apk -o /tmp/
adb install TunProxy.apk
Option B: Per-app proxy injection via Frida
frida -U -f com.target.app -l proxy-redirect.js --no-pause
iOS Flutter.framework patching
unzip target.ipa -d /tmp/ipa-out/
ls /tmp/ipa-out/Payload/TargetApp.app/Frameworks/Flutter.framework/Flutter
reflutter target.ipa
codesign --force --sign - /tmp/ipa-out/Payload/TargetApp.app/Frameworks/Flutter.framework/Flutter
Step 3: BoringSSL Bypass Mechanism
reFlutter patches ssl_crypto_x509_session_verify_cert_chain to
unconditionally return 1 (success). This function is the central
chain verification entry point in BoringSSL (the TLS library embedded
in libflutter.so) — no root CA, no pinning config, and no custom
validator override this patch because the chain evaluation never runs.
Manual binary patch (if reFlutter fails on a specific version):
r2 -A /tmp/apk-out/lib/arm64-v8a/libflutter.so
python3 -c "
import struct
with open('libflutter.so', 'r+b') as f:
f.seek(<offset>)
f.write(b'\x20\x00\x80\x52\xC0\x03\x5F\xD6')
"
Step 4: Static Native RE — libapp.so in Ghidra / radare2
r2 -A /tmp/apk-out/lib/arm64-v8a/libapp.so
r2 -qc 'iz~https' /tmp/apk-out/lib/arm64-v8a/libapp.so
r2 -qc 'iz~firebase' /tmp/apk-out/lib/arm64-v8a/libapp.so
r2 -qc 'iz~AIza' /tmp/apk-out/lib/arm64-v8a/libapp.so
r2 -qc 'ii' /tmp/apk-out/lib/arm64-v8a/libapp.so | head -40
In Ghidra (MCP-connected via ghidra server):
# connect_instance first, then batch analyze
# Load libapp.so → auto-analyze
# Search defined strings for URL patterns, credential patterns
# Dart AOT functions are not named — use string xrefs to find handlers
Dart AOT functions lack names (no symbol table in release builds) but
the Dart snapshot contains type metadata that Il2CppDumper-style
tools and dart_vm snapshot parsers can partially recover (see
snapshot dump section below).
Step 5: Dart Snapshot Dump (Hit-or-Miss)
adb shell "run-as com.target.app cat /data/data/com.target.app/app_flutter/snapshot_blob.bin" \
> /tmp/snapshot_blob.bin 2>/dev/null
pip install dart-snapshot-parser 2>/dev/null || true
Caveats: Dart snapshot format changed in Dart 2.15, 3.0, and 3.4.
Community parsers (snapshot_inspector, etc.) are often version-specific
and may produce partial or no output on latest Flutter. Treat snapshot
dump as a best-effort step; static string analysis is more reliable.
Step 6: Flutter Plugin Identification
Flutter plugins use platform channels with predictable naming:
strings /tmp/apk-out/lib/arm64-v8a/libapp.so | grep -E "plugins\.|flutter\." | sort -u
cat /tmp/apk-out/flutter_assets/AssetManifest.json | python3 -m json.tool | head
Evidence
kg_add_node(
kind="finding",
label="Flutter BoringSSL pinning bypassed",
props={
"key": f"flutter-boringssl-bypass::{package_id}",
"severity": "high",
"cvss": 7.4,
"package": package_id,
"flutter_version": "<version-from-libflutter-strings>",
"bypass_method": "reFlutter",
"proxy_verified": True,
},
)
ZFP
- Burp HTTP history showing decrypted HTTPS from patched Flutter app.
strings libapp.so | grep https output showing extracted endpoints.
- Screenshot of reFlutter build output + uber-apk-signer signing
confirming the patched APK was installed.
OPSEC Notes
- reFlutter requires re-signing the APK; app integrity checks (Google
Play Integrity API, SafetyNet) will flag the modified signature.
Use emulator or device without Play Store for testing.
- The patched APK has a different certificate than the original; side-
load via
adb install or adb install --bypass-low-target-sdk-block.
- For production devices, TunProxy routes traffic at the VPN layer
without modifying the APK signature.
- libflutter.so binary patching is version-specific; wrong offset
crashes the app. Always test on a throwaway device/emulator first.
Severity Table
| Bug | Severity |
|---|
| No certificate validation (BoringSSL patched trivially) | High 7.4 |
| Hardcoded API key / Firebase config in libapp.so strings | Critical 9.0 |
| Sensitive data in Dart snapshot / kernel_blob | High 7.5 |
| Flutter plugin channel unauthenticated method call | Medium-High |
References