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 <hash>` | 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)