Skip to main content

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.

ソース情報

リポジトリ
BitterSecurity/Decepticon
ソースの最終更新活動
2026年5月30日 11:41
検出された SKILL.md の言語
英語
スター
5,611
フォーク
1,061

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
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 ```bash # 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) ```bash # 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** ```bash # 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** ```bash # 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 ```bash # 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):** ```bash # 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 ```bash # 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) ```bash # 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: ```bash # 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 ```python 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 | 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 - 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)
GitHubで見る