CtrlK
BlogDocsLog inGet started
Tessl Logo

flutter

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

Quality

73%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/mobile/flutter/SKILL.md
SKILL.md
Quality
Evals
Security

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

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)

# 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 Burp

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

# 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 app

Option 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

iOS Flutter.framework patching

# 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 Sideloadly

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):

# 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')
"

Step 4: Static Native RE — libapp.so in Ghidra / radare2

# 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 -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)

# 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 tools

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:

# 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 | 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

  1. Burp HTTP history showing decrypted HTTPS from patched Flutter app.
  2. strings libapp.so | grep https output showing extracted endpoints.
  3. 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

BugSeverity
No certificate validation (BoringSSL patched trivially)High 7.4
Hardcoded API key / Firebase config in libapp.so stringsCritical 9.0
Sensitive data in Dart snapshot / kernel_blobHigh 7.5
Flutter plugin channel unauthenticated method callMedium-High

References

  • reFlutter: https://github.com/ptswarm/reFlutter
  • Flutter 3.24.0 proxy change: https://github.com/ptswarm/reFlutter/issues/107
  • TunProxy (Android): https://github.com/raise-isayan/TunProxy
  • uber-apk-signer: https://github.com/patrickfav/uber-apk-signer
  • kayssel "Breaking Flutter" guide: https://blog.nviso.eu/2022/08/18/intercept-flutter-traffic-on-ios-and-android-http-https-edition/
  • Cross-ref: mobile/android/SKILL.md (APK pull), mobile/ios/dynamic/SKILL.md (iOS BoringSSL path)
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

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.