diff --git a/src/mobile-pentesting/android-app-pentesting/flutter.md b/src/mobile-pentesting/android-app-pentesting/flutter.md index 1657f597b67..21bcbd3c3d4 100644 --- a/src/mobile-pentesting/android-app-pentesting/flutter.md +++ b/src/mobile-pentesting/android-app-pentesting/flutter.md @@ -15,15 +15,17 @@ This is a summary of this [blog post](https://sensepost.com/blog/2025/intercepti * Symbols in libflutter.so are **stripped & mangled**, hiding the certificate-verification function from dynamic tools. ### Fingerprint the exact Flutter stack -Knowing the version lets you re-build or pattern-match the right binaries. +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. Step | Command / File | Outcome ----|----|---- -Get snapshot hash | `python3 get_snapshot_hash.py libapp.so` | `adb4292f3ec25…` -Map hash → Engine | **enginehash** list in reFlutter | Flutter 3 · 7 · 12 + engine commit `1a65d409…` -Pull dependent commits | DEPS file in that engine commit | • `dart_revision` → Dart v2 · 19 · 6
• `dart_boringssl_rev` → BoringSSL `87f316d7…` +Check packaging | `unzip -l app.apk | grep -E "libapp.so|libflutter.so"` | Confirm the target ABI and that both Flutter libraries are really present +Get snapshot hash | `python3 get_snapshot_hash.py libapp.so` | Engine snapshot hash (**use `libapp.so`, not `libflutter.so`**) +Map hash → Engine | `curl -s https://raw.githubusercontent.com/Impact-I/reFlutter/refs/heads/main/enginehash.csv | grep ` | Flutter version + engine commit +If hash is missing | `python3 gen_enginehash.py` in reFlutter `scripts/` | Fresh local hash table for newer/debug builds +Pull dependent commits | Flutter release `DEPS` file | Exact `dart_boringssl_rev` / BoringSSL commit -Find [get_snapshot_hash.py here](https://github.com/Impact-I/reFlutter/blob/main/scripts/get_snapshot_hash.py). +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`. ### Target: `ssl_crypto_x509_session_verify_cert_chain()` * Located in **`ssl_x509.cc`** inside BoringSSL. @@ -116,32 +118,28 @@ Works on iOS via a Frida gadget or USB frida-server; chaining the socket redirec 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. ### Offset-based hook of BoringSSL verification (no signature scan) -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. Workflow: +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: -- Extract the right-ABI library from the APK: `unzip -j app.apk "lib/*/libflutter.so" -d libs/` and pick the one matching the device (e.g., `lib/x86_64/libflutter.so`). -- Analyze in Ghidra/IDA and locate the verifier: - - Source: BoringSSL ssl_x509.cc function `ssl_crypto_x509_session_verify_cert_chain` (3 args, returns bool). - - In stripped builds, use **Search → For Strings → `ssl_client` → XREFs**, then open each referenced `FUN_...` and pick the one with 3 pointer-like args and a boolean return. -- Compute the runtime offset: take the function address shown by Ghidra and subtract the image base (e.g., Ghidra often shows `0x00100000` for PIE Android ELFs). Example: `0x02184644 - 0x00100000 = 0x02084644`. +- Extract the right-ABI library from the APK: `unzip -j app.apk "lib/*/libflutter.so" -d libs/` and pick the one matching the device. +- Resolve the exact BoringSSL revision from the matching Flutter `DEPS`, fetch `ssl/ssl_x509.cc`, and identify `ssl_crypto_x509_session_verify_cert_chain`. +- 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. +- 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. +- Compute the runtime offset: `function_address - image_base`. Example: `0x00840950 - 0x00100000 = 0x00740950`. - Hook at runtime by base + offset and force success: ```javascript -// frida -U -f com.target.app -l bypass.js --no-pause -const base = Module.findBaseAddress('libflutter.so'); -// Example offset from analysis. Recompute per build/arch. -const off = ptr('0x02084644'); -const addr = base.add(off); - -// ssl_crypto_x509_session_verify_cert_chain: 3 args, bool return -Interceptor.replace(addr, new NativeCallback(function (a, b, c) { - return 1; // true -}, 'int', ['pointer', 'pointer', 'pointer'])); - -console.log('[+] Hooked BoringSSL verify_cert_chain at', addr); +const module = Process.findModuleByName('libflutter.so'); +const addr = module.base.add(ptr('0x00740950')); // recompute per build/arch +Interceptor.attach(addr, { + onLeave(retval) { + retval.replace(0x1); + } +}); ``` Notes - 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. +- 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). - This bypass causes BoringSSL to accept any chain, enabling HTTPS MITM regardless of pins/CA trust inside Flutter. - If you force-route traffic during debugging to confirm TLS blocking, e.g.: @@ -149,7 +147,7 @@ Notes iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination : ``` -…you will still need the hook above, since verification happens inside libflutter.so, not Android’s system trust store. +…you will still need the hook above, since verification happens inside `libflutter.so`, not Android’s system trust store. ## Reversing the Dart payload (`libapp.so`) @@ -228,6 +226,8 @@ Those artifacts can turn a “hard to read” AOT binary back into a much friend ## References - [https://sensepost.com/blog/2025/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/) +- [Bypassing Flutter TLS/SSL Verification When reFlutter Fails](https://petruknisme.medium.com/bypassing-flutter-tls-ssl-verification-when-reflutter-fails-a4c41ff758a3) +- [Impact-I/reFlutter](https://github.com/Impact-I/reFlutter) - [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/) - [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) - [PoC Frida hook for Flutter SSL bypass](https://github.com/m4kr0x/flutter_ssl_bypass)