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.
61
73%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/mobile/flutter/SKILL.mdFlutter compiles Dart AOT to a native shared library (
libapp.so/Flutter.framework).jadx/apktoolshow 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.
mobile/android/SKILL.md Path 1 for APK pull).reflutter (pip install reflutter), uber-apk-signer
(download JAR from GitHub releases), adb + device or emulator,
jadx (identification only), Burp Suite proxy.reFlutter also patches Flutter.framework; use
objection patchipa or direct framework patching workflow.# Unzip APK, look for the telltale Flutter artifacts
unzip -o base.apk -d /tmp/apk-out/
ls /tmp/apk-out/lib/arm64-v8a/
# Flutter app: libflutter.so libapp.so
# Present: flutter_assets/ kernel_blob.bin (optional, debug)
# jadx shows only the Java bootstrap:
jadx -d /tmp/apk-java /tmp/base.apk
# Expect: ~1 class, LoadLibrary("app"), nothing useful
# Confirm with strings:
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).
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).
# Patch: supply your Burp listener IP
reflutter base.apk
# reFlutter prompts for IP — enter your Burp machine IP (e.g. 192.168.1.100)
# Output: release.RE.apk (patched, unsigned)
# Sign with uber-apk-signer
java -jar uber-apk-signer.jar --allowResign -a release.RE.apk -o /tmp/
# Install on device
adb install /tmp/release.RE-aligned-signed.apk
# Configure Burp proxy listener on 0.0.0.0:8083 (reFlutter default port is 8083)
# Launch app — HTTPS traffic appears in BurpStarting 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
# Patch as above (no IP prompt in 3.24+)
reflutter base.apk
java -jar uber-apk-signer.jar --allowResign -a release.RE.apk -o /tmp/
# Install and configure device proxy manually:
# Android Settings → Wi-Fi → [SSID] → Proxy → Manual
# Host: <Burp-IP> Port: 8080
# Or use TunProxy for non-proxy-aware processes:
adb install TunProxy.apk
# Launch TunProxy, set server to <Burp-IP>:8080, toggle VPN
# Then launch patched appOption B: Per-app proxy injection via Frida
# Run patched APK + Frida script to redirect all socket connections
frida -U -f com.target.app -l proxy-redirect.js --no-pause
# proxy-redirect.js: hooks connect() syscall, redirects to Burp IP# Extract IPA
unzip target.ipa -d /tmp/ipa-out/
# Locate Flutter.framework
ls /tmp/ipa-out/Payload/TargetApp.app/Frameworks/Flutter.framework/Flutter
# reFlutter iOS (experimental — check reFlutter README for current support)
reflutter target.ipa
# Sign with codesign + adhoc or with valid mobileprovision
codesign --force --sign - /tmp/ipa-out/Payload/TargetApp.app/Frameworks/Flutter.framework/Flutter
# Repack + install with ios-deploy or SideloadlyreFlutter 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):
# Find the function offset in libflutter.so
r2 -A /tmp/apk-out/lib/arm64-v8a/libflutter.so
# In r2: afl~ssl_crypto_x509
# Or: /c ret in data section near ssl_crypto_x509_session_verify_cert_chain
# Patch: overwrite function prologue with MOV W0, #1 / RET (AArch64)
# MOV W0, 1 = 20 00 80 52
# RET = C0 03 5F D6
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')
"# Load libapp.so in radare2 for quick triage
r2 -A /tmp/apk-out/lib/arm64-v8a/libapp.so
# Strings — API endpoints, Firebase config, hardcoded keys
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 # Firebase API key prefix
# Imports — identify Flutter plugins with native bridges
r2 -qc 'ii' /tmp/apk-out/lib/arm64-v8a/libapp.so | head -40In 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 handlersDart 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).
# Option 1: runtime adb pull from memory-mapped snapshot
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
# Option 2: use Dart VM snapshot reader tools
# dart_snapshot_parser (community; partial support for Dart 2.x-3.x)
pip install dart-snapshot-parser 2>/dev/null || true
# Option 3: reFlutter's snapshot output
# After running reFlutter and launching the patched app once,
# reFlutter dumps snapshot_hash.txt which can seed symbol recovery toolsCaveats: 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.
Flutter plugins use platform channels with predictable naming:
# Find channel names in libapp.so strings
strings /tmp/apk-out/lib/arm64-v8a/libapp.so | grep -E "plugins\.|flutter\." | sort -u
# Channel names like:
# com.google.firebase.messaging → push tokens
# plugins.flutter.io/path_provider → storage paths
# flutter.baseflow.com/geolocator → GPS
# plugins.flutter.io/local_auth → biometric
# Cross-reference with flutter_assets/AssetManifest.json
cat /tmp/apk-out/flutter_assets/AssetManifest.json | python3 -m json.tool | headkg_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,
},
)strings libapp.so | grep https output showing extracted endpoints.adb install or adb install --bypass-low-target-sdk-block.| 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 |
mobile/android/SKILL.md (APK pull), mobile/ios/dynamic/SKILL.md (iOS BoringSSL path)31e1c8e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.