daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

flutter.md (15751B)


      1 ---
      2 title: "Flutter"
      3 section: "Mobile"
      4 sectionSlug: "mobile-pentesting"
      5 sourcePath: "src/mobile-pentesting/android-app-pentesting/flutter.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/flutter.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Flutter
     14 
     15 Flutter is **Google’s cross-platform UI toolkit** that lets developers write a single Dart code-base which the **Engine** (native C/C++) turns into platform-specific machine code for Android & iOS.  
     16 The Engine bundles a **Dart VM**, **BoringSSL**, Skia, etc., and ships as the shared library **libflutter.so** (Android) or **Flutter.framework** (iOS). All actual networking (DNS, sockets, TLS) happens **inside this library**, *not* in the usual Java/Kotlin Swift/Obj-C layers. That siloed design is why the usual Java-level Frida hooks fail on Flutter apps.
     17 
     18 ## Intercepting HTTPS traffic in Flutter
     19 
     20 This is a summary of this [blog post](https://sensepost.com/blog/2025/intercepting-https-communication-in-flutter-going-full-hardcore-mode-with-frida/).<sup>[[1]](#references)</sup>
     21 
     22 ### Why HTTPS interception is tricky in Flutter  
     23 * **SSL/TLS verification lives two layers down** in BoringSSL, so Java SSL‐pinning bypasses don’t touch it.  
     24 * **BoringSSL uses its *own* CA store** inside libflutter.so; importing your Burp/ZAP CA into Android’s system store changes nothing.  
     25 * Symbols in libflutter.so are **stripped & mangled**, hiding the certificate-verification function from dynamic tools.
     26 
     27 ### Fingerprint the exact Flutter stack  
     28 When **reFlutter** cannot auto-patch the APK (unsupported engine hash, debug engine, missing `libapp.so`, newer release), avoid guessing offsets and derive the exact Flutter/BoringSSL pair first.<sup>[[2]](#references)</sup>
     29 
     30 Step | Command / File | Outcome
     31 ----|----|----
     32 Check packaging | `unzip -l app.apk | grep -E "libapp.so|libflutter.so"` | Confirm the target ABI and that both Flutter libraries are really present
     33 Get snapshot hash | `python3 get_snapshot_hash.py libapp.so` | Engine snapshot hash (**use `libapp.so`, not `libflutter.so`**)
     34 Map hash → Engine | `curl -s https://raw.githubusercontent.com/Impact-I/reFlutter/refs/heads/main/enginehash.csv \| grep &lt;hash&gt;` | Flutter version + engine commit
     35 If hash is missing | `python3 gen_enginehash.py` in reFlutter `scripts/` | Fresh local hash table for newer/debug builds
     36 Pull dependent commits | Flutter release `DEPS` file | Exact `dart_boringssl_rev` / BoringSSL commit
     37 
     38 Find [get_snapshot_hash.py here](https://github.com/Impact-I/reFlutter/blob/main/scripts/get_snapshot_hash.py). A bogus hash such as repeated `4` values usually means the script was run against `libflutter.so` instead of `libapp.so`.<sup>[[2]](#references)[[3]](#references)</sup>
     39 
     40 ### Target: `ssl_crypto_x509_session_verify_cert_chain()`  
     41 * Located in **`ssl_x509.cc`** inside BoringSSL.  
     42 * **Returns `bool`** – a single `true` is enough to bypass the whole certificate chain check.  
     43 * Same function exists on every CPU arch; only the opcodes differ.<sup>[[7]](#references)</sup>
     44 
     45 ### Option A – Binary patching with **reFlutter**  
     46 1. **Clone** the exact Engine & Dart sources for the app’s Flutter version.
     47 2. **Regex-patch** two hotspots:
     48    * In `ssl_x509.cc`, force `return 1;`  
     49    * (Optional) In `socket_android.cc`, hard-code a proxy (`"10.0.2.2:8080"`).
     50 3. **Re-compile** libflutter.so, drop it back into the APK/IPA, sign, install.
     51 4. **Pre-patched builds** for common versions are shipped in the reFlutter GitHub releases to save hours of build time.<sup>[[3]](#references)</sup>
     52 
     53 ### Option B – Live hooking with **Frida** (the “hard-core” path)  
     54 Because the symbol is stripped, you pattern-scan the loaded module for its first bytes, then change the return value on the fly.
     55 
     56 ```javascript
     57 // attach & locate libflutter.so
     58 var flutter = Process.getModuleByName("libflutter.so");
     59 
     60 // x86-64 pattern of the first 16 bytes of ssl_crypto_x509_session_verify_cert_chain
     61 var sig = "55 41 57 41 56 41 55 41 54 53 48 83 EC 38 C6 02";
     62 
     63 Memory.scan(flutter.base, flutter.size, sig, {
     64   onMatch: function (addr) {
     65     console.log("[+] found verifier at " + addr);
     66     Interceptor.attach(addr, {
     67       onLeave: function (retval) { retval.replace(0x1); }  // always 'true'
     68     });
     69   },
     70   onComplete: function () { console.log("scan done"); }
     71 });
     72 ```
     73 
     74 Run it:
     75 
     76 ```bash
     77 frida -U -f com.example.app -l bypass.js
     78 ```
     79 
     80 *Porting tips*  
     81 * For **arm64-v8a** or **armv7**, grab the first ~32 bytes of the function from Ghidra, convert to a space-separated hex string, and replace `sig`.  
     82 * Keep **one pattern per Flutter release**, store them in a cheat-sheet for fast reuse.
     83 
     84 ### Forcing traffic through your proxy  
     85 Flutter itself **ignores device proxy settings**. Easiest options:  
     86 * **Android Studio emulator:** Settings ▶ Proxy → manual.  
     87 * **Physical device:** evil Wi-Fi AP + DNS spoofing, or Magisk module editing `/etc/hosts`.
     88 
     89 ### Quick Flutter TLS bypass workflow (Frida Codeshare + system CA)  
     90 When you only need to observe a pinned Flutter API, combining a rooted/writable AVD, a system-trusted proxy CA, and a drop-in Frida script is often faster than reverse-engineering libflutter.so:
     91 
     92 1. **Install your proxy CA in the system store.** Follow [Install Burp Certificate](/hacktricks/mobile-pentesting/android-app-pentesting/install-burp-certificate) to hash/rename Burp's DER certificate and push it into `/system/etc/security/cacerts/` (writable `/system` required).<sup>[[8]](#references)</sup>
     93 
     94 2. **Drop a matching `frida-server` binary and run it as root** so it can attach to the Flutter process:<sup>[[8]](#references)</sup>
     95 
     96 ```bash
     97 adb push frida-server-17.0.5-android-x86_64 /data/local/tmp/frida-server
     98 adb shell "su -c 'chmod 755 /data/local/tmp/frida-server && /data/local/tmp/frida-server &'"
     99 ```
    100 
    101 3. **Install the host-side tooling and enumerate the target package.**<sup>[[8]](#references)</sup>
    102 
    103 ```bash
    104 pip3 install frida-tools --break-system-packages
    105 adb shell pm list packages -f | grep target
    106 ```
    107 
    108 4. **Spawn the Flutter app with the Codeshare hook that neuters BoringSSL pin checks.**<sup>[[8]](#references)</sup>
    109 
    110 ```bash
    111 frida -U -f com.example.target --codeshare TheDauntless/disable-flutter-tls-v1 --no-pause
    112 ```
    113 
    114 The Codeshare script overrides the Flutter TLS verifier so every certificate (including Burp's dynamically generated ones) is accepted, side-stepping public-key pin comparisons.
    115 
    116 5. **Route traffic through your proxy.** Configure the emulator Wi-Fi proxy GUI or enforce it via `adb shell settings put global http_proxy 10.0.2.2:8080`; if direct routing fails, fall back to `adb reverse tcp:8080 tcp:8080` or a host-only VPN.
    117 
    118 6. **If the app ignores OS proxy settings, redirect sockets with a Frida shim.** Tools like **frida4burp** hook `dart:io`/BoringSSL socket creation to force outbound TCP sessions to your proxy, even with hardcoded `HttpClient.findProxyFromEnvironment` or Wi‑Fi bypasses. Set the proxy host/port in the script and run it alongside the TLS bypass:<sup>[[9]](#references)</sup>
    119 
    120 ```bash
    121 frida -U -f com.example.target --no-pause \
    122   --codeshare TheDauntless/disable-flutter-tls-v1 \
    123   -l frida4burp.js
    124 ```
    125 
    126 Works on iOS via a Frida gadget or USB frida-server; chaining the socket redirect with the TLS bypass restores both routing and certificate acceptance for Burp/mitmproxy.
    127 
    128 Once the CA is trusted at the OS layer and Frida quashes Flutter's pinning logic (plus socket redirection if needed), Burp/mitmproxy regains full visibility for API fuzzing (BOLA, token tampering, etc.) without repacking the APK.
    129 
    130 ### Offset-based hook of BoringSSL verification (no signature scan)
    131 When pattern-based scripts fail across architectures (e.g., x86_64 vs ARM), directly hook the BoringSSL chain verifier by absolute address within `libflutter.so`. A reliable workflow is:<sup>[[2]](#references)</sup>
    132 
    133 - Extract the right-ABI library from the APK: `unzip -j app.apk "lib/*/libflutter.so" -d libs/` and pick the one matching the device.
    134 - Resolve the exact BoringSSL revision from the matching Flutter `DEPS`, fetch `ssl/ssl_x509.cc`, and identify `ssl_crypto_x509_session_verify_cert_chain`.
    135 - Use the line number of the `OPENSSL_PUT_ERROR(...)` inside that exact source revision as a **scalar search anchor** in Ghidra. The line number is compiled into the error path, so searching for that constant (for example `239`) is often faster than guessing patterns in a stripped binary.
    136 - If the scalar search is noisy, fall back to **Search → For Strings → `ssl_client` → XREFs** and keep only candidates that decompile into a 3-argument function matching `SSL_SESSION *`, `SSL_HANDSHAKE *`, `uint8_t *` semantics.<sup>[[4]](#references)[[5]](#references)[[6]](#references)</sup>
    137 - Compute the runtime offset: `function_address - image_base`. Example: `0x00840950 - 0x00100000 = 0x00740950`.
    138 - Hook at runtime by base + offset and force success:
    139 
    140 ```javascript
    141 const module = Process.findModuleByName('libflutter.so');
    142 const addr = module.base.add(ptr('0x00740950')); // recompute per build/arch
    143 Interceptor.attach(addr, {
    144   onLeave(retval) {
    145     retval.replace(0x1);
    146   }
    147 });
    148 ```
    149 
    150 Notes
    151 - Signature scans can succeed on ARM but miss on x86_64 because the opcode layout changes; this offset method is architecture-agnostic as long as you recalc the RVA.
    152 - If `libflutter.so` is not loaded yet, install the hook after the Android linker resolves it (commonly by watching `linker64` symbols such as `do_dlopen` / `call_constructor` and calling your attach routine once `libflutter.so` appears).
    153 - This bypass causes BoringSSL to accept any chain, enabling HTTPS MITM regardless of pins/CA trust inside Flutter.
    154 - If you force-route traffic during debugging to confirm TLS blocking, e.g.:
    155 
    156 ```bash
    157 iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination <Burp_IP>:<Burp_Port>
    158 ```
    159 
    160 …you will still need the hook above, since verification happens inside `libflutter.so`, not Android’s system trust store.
    161 
    162 
    163 ## Reversing the Dart payload (`libapp.so`)
    164 
    165 ### Quick static triage
    166 Before rebuilding `libflutter.so`, spend two minutes identifying what is actually packaged inside the APK:
    167 
    168 ```bash
    169 unzip -l target.apk | grep -E "(libflutter|libapp|flutter_assets|kernel_blob)"
    170 unzip -j target.apk "lib/arm64-v8a/libapp.so" "lib/arm64-v8a/libflutter.so" -d extracted_libs/
    171 strings extracted_libs/libflutter.so | grep -E "^[0-9]+\.[0-9]+\.[0-9]+" | head
    172 ```
    173 
    174 - `libapp.so` is the AOT-compiled Dart payload.
    175 - `assets/flutter_assets/` usually contains manifests, JSON/config files, and sometimes secrets or hidden feature flags.
    176 - `kernel_blob.bin` is a high-value indicator of a debug/non-release build; if present, prioritize it before spending time on AOT reversing.
    177 
    178 A quick first pass that regularly pays off:
    179 
    180 ```bash
    181 apktool d target.apk -o apktool-out
    182 rg -n -a "(https?://|api[_-]?key|token|secret|BEGIN (RSA|EC|PRIVATE)|supabase|stripe|aws)" \
    183   apktool-out/assets/flutter_assets extracted_libs -S
    184 ```
    185 
    186 ### Recovering symbols, strings and hook points with **blutter**
    187 For ARM64 targets, **blutter** is currently the most practical first step because it parses the Dart AOT snapshot, labels functions, dumps the object pool, and generates Frida hook stubs.<sup>[[10]](#references)</sup> For generic ELF work after that, check [Reversing Native Libraries](/hacktricks/mobile-pentesting/android-app-pentesting/reversing-native-libraries).
    188 
    189 ```bash
    190 python3 blutter.py extracted_libs/ blutter-out
    191 rg -n -a "(https?://|api[_-]?key|token|secret|BEGIN (RSA|EC|PRIVATE)|supabase|stripe|aws)" \
    192   blutter-out/pp.txt blutter-out/asm -S
    193 gitleaks detect -s blutter-out/pp.txt --no-git
    194 ```
    195 
    196 Useful outputs:
    197 - `asm/`: annotated assembly with recovered Dart/library names
    198 - `pp.txt`: object-pool dump; often the fastest place to find endpoints, JWTs, keys, IVs, feature flags, and C2 paths
    199 - `blutter_frida.js`: starter hooks you can adapt to print arguments or return values from recovered functions
    200 
    201 If you want a **fully static** workflow or a fast diff between two releases, newer tools like `unflutter` and `flutterdec` are worth trying, but `blutter` remains the quickest route from APK → named functions → Frida hook points.
    202 
    203 ### Platform / MethodChannel reconnaissance
    204 A lot of Flutter-specific attack surface is not in TLS at all but in the bridge between Dart and the Android host. The most valuable strings are often **MethodChannel / EventChannel names** because they point to privileged native actions (contacts, SMS, files, biometrics, device identifiers, push tokens, custom crypto helpers, etc.).
    205 
    206 ```bash
    207 jadx -r target.apk -d jadx-out
    208 rg -n "MethodChannel|EventChannel|BasicMessageChannel|plugins\\.flutter\\.io|setMethodCallHandler|invokeMethod" jadx-out -S
    209 ```
    210 
    211 Frida can log channel creation names very early during startup:
    212 
    213 ```javascript
    214 Java.perform(function () {
    215   var MC = Java.use('io.flutter.plugin.common.MethodChannel');
    216   MC.$init.overload('io.flutter.plugin.common.BinaryMessenger', 'java.lang.String')
    217     .implementation = function (messenger, name) {
    218       console.log('[+] MethodChannel: ' + name);
    219       return this.$init(messenger, name);
    220     };
    221 });
    222 ```
    223 
    224 Once you know the channel name, pivot into the matching Java/Kotlin handler and the Dart caller in `blutter-out/asm/` to trace how sensitive data moves across the bridge.
    225 
    226 ### Obfuscation notes for pentesters
    227 Flutter obfuscation (`--obfuscate --split-debug-info`) only renames symbols and stores the symbol map externally so developers can later run `flutter symbolize`. It does **not** encrypt `flutter_assets`, prevent native disassembly of `libapp.so`, or stop runtime hooks.<sup>[[11]](#references)</sup>
    228 
    229 If your assessment includes CI/CD artifacts, crash-upload buckets, update infrastructure, or mobile build pipelines, search for accidentally exposed:
    230 - `app.*.symbols`
    231 - `SYMBOLS`
    232 - obfuscation-map JSON files
    233 - directories used as `--split-debug-info` output
    234 
    235 Those artifacts can turn a “hard to read” AOT binary back into a much friendlier target.
    236 
    237 ## References
    238 - [1] [Intercepting HTTPS Communication in Flutter: Going Full Hardcore Mode with Frida](https://sensepost.com/blog/2025/intercepting-https-communication-in-flutter-going-full-hardcore-mode-with-frida/)
    239 - [2] [Bypassing Flutter TLS/SSL Verification When reFlutter Fails](https://petruknisme.medium.com/bypassing-flutter-tls-ssl-verification-when-reflutter-fails-a4c41ff758a3)
    240 - [3] [Impact-I/reFlutter](https://github.com/Impact-I/reFlutter)
    241 - [4] [Flutter SSL Bypass: How to Intercept HTTPS Traffic When all other Frida Scripts Fail (vercel)](https://m4kr0.vercel.app/posts/flutter-ssl-bypass-how-to-intercept-https-traffic-when-all-other-frida-scripts-fail/)
    242 - [5] [Flutter SSL Bypass: How to Intercept HTTPS Traffic When all other Frida Scripts Fail (medium)](https://m4kr0x.medium.com/flutter-tls-bypass-how-to-intercept-https-traffic-when-all-other-frida-scripts-fail-bd3d04489088)
    243 - [6] [PoC Frida hook for Flutter SSL bypass](https://github.com/m4kr0x/flutter_ssl_bypass)
    244 - [7] [BoringSSL ssl_x509.cc (ssl_crypto_x509_session_verify_cert_chain)](https://github.com/google/boringssl/blob/main/ssl/ssl_x509.cc#L238)
    245 - [8] [SSL Pinning Bypass – Android](https://hardsoftsecurity.es/index.php/2025/11/26/ssl-pinning-bypass-android/)
    246 - [9] [Practical Mobile Traffic Interception](https://medium.com/@justmobilesec/practical-mobile-traffic-interception-1481e33d974e)
    247 - [10] [B(l)utter - Flutter Mobile Application Reverse Engineering Tool](https://github.com/worawit/blutter)
    248 - [11] [Flutter official docs - Obfuscate Dart code](https://docs.flutter.dev/deployment/obfuscate)