84 lines
4.2 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flutter
{{#include ../../banners/hacktricks-training.md}}
# Flutter
Flutter is **Googles 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.
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.
## Intercepting HTTPS traffic in Flutter
This is a summary of this [blog post](https://sensepost.com/blog/2025/intercepting-https-communication-in-flutter-going-full-hardcore-mode-with-frida/).
### Why HTTPS interception is tricky in Flutter
* **SSL/TLS verification lives two layers down** in BoringSSL, so Java SSLpinning bypasses dont touch it.
* **BoringSSL uses its *own* CA store** inside libflutter.so; importing your Burp/ZAP CA into Androids system store changes nothing.
* 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.
Step | Command / File | Outcome
----|----|----
Get snapshot hash | ```bash\npython3 get_snapshot_hash.py libapp.so\n``` | `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<br>• `dart_boringssl_rev` → BoringSSL `87f316d7…`
Find [get_snapshot_hash.py here](https://github.com/Impact-I/reFlutter/blob/main/scripts/get_snapshot_hash.py).
### Target: `ssl_crypto_x509_session_verify_cert_chain()`
* Located in **`ssl_x509.cc`** inside BoringSSL.
* **Returns `bool`** a single `true` is enough to bypass the whole certificate chain check.
* Same function exists on every CPU arch; only the opcodes differ.
### Option A Binary patching with **reFlutter**
1. **Clone** the exact Engine & Dart sources for the apps Flutter version.
2. **Regex-patch** two hotspots:
* In `ssl_x509.cc`, force `return 1;`
* (Optional) In `socket_android.cc`, hard-code a proxy (`"10.0.2.2:8080"`).
3. **Re-compile** libflutter.so, drop it back into the APK/IPA, sign, install.
4. **Pre-patched builds** for common versions are shipped in the reFlutter GitHub releases to save hours of build time.
### Option B Live hooking with **Frida** (the “hard-core” path)
Because the symbol is stripped, you pattern-scan the loaded module for its first bytes, then change the return value on the fly.
```javascript
// attach & locate libflutter.so
var flutter = Process.getModuleByName("libflutter.so");
// x86-64 pattern of the first 16 bytes of ssl_crypto_x509_session_verify_cert_chain
var sig = "55 41 57 41 56 41 55 41 54 53 48 83 EC 38 C6 02";
Memory.scan(flutter.base, flutter.size, sig, {
onMatch: function (addr) {
console.log("[+] found verifier at " + addr);
Interceptor.attach(addr, {
onLeave: function (retval) { retval.replace(0x1); } // always 'true'
});
},
onComplete: function () { console.log("scan done"); }
});
```
Run it:
```bash
frida -U -f com.example.app -l bypass.js
```
*Porting tips*
* 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`.
* Keep **one pattern per Flutter release**, store them in a cheat-sheet for fast reuse.
### Forcing traffic through your proxy
Flutter itself **ignores device proxy settings**. Easiest options:
* **Android Studio emulator:** Settings ▶ Proxy → manual.
* **Physical device:** evil Wi-Fi AP + DNS spoofing, or Magisk module editing `/etc/hosts`.
## 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/)
{{#include ../../banners/hacktricks-training.md}}