Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions src/mobile-pentesting/android-app-pentesting/flutter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<br>• `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 <hash>` | 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.
Expand Down Expand Up @@ -116,40 +118,36 @@ 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.:

```bash
iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination <Burp_IP>:<Burp_Port>
```

…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`)
Expand Down Expand Up @@ -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)
Expand Down