Listen to this Post

Introduction:
Flutter applications present a unique challenge for mobile penetration testers because they handle TLS verification natively within the `libflutter.so` library rather than relying on the Android or iOS system CA store. Standard SSL pinning bypass techniques—whether through Frida scripts, Xposed modules, or automated tools like reFlutter—often fail when architecture mismatches or Flutter engine versions introduce different memory layouts and function offsets. This article documents a systematic approach to bypassing Flutter SSL pinning through manual binary reverse engineering with Ghidra, providing a reliable fallback when automated tools fall short.
Learning Objectives:
- Understand why Flutter applications bypass traditional SSL interception methods and how BoringSSL is embedded within `libflutter.so`
– Learn to manually locate the certificate verification function using Ghidra static analysis - Master the technique of calculating module-relative offsets and implementing Frida hooks to force TLS verification to succeed
- Recognize the critical impact of architecture (ARM vs. x86_64) on memory offsets and script compatibility
1. Understanding Why Flutter Breaks Normal Bypasses
Flutter apps are written in Dart, and Dart’s networking stack does not use the system CA store. Instead, Flutter embeds BoringSSL—Google’s fork of OpenSSL—directly within the Flutter engine, compiled into `libflutter.so` on Android. When the application validates a TLS certificate chain, the verification flow eventually reaches a BoringSSL routine responsible for validating the certificate chain and returning either success or failure.
This architectural decision means that:
- Traditional certificate pinning bypass tools (JustTrustMe, SSLUnpinning, etc.) have no effect
- The system proxy settings are ignored—Flutter does not use them by default
- Frida scripts that work on one architecture may silently fail on another due to different memory layouts
The common failure scenario begins with Burp Suite showing zero requests—the app’s TLS verification blocks the traffic before it ever reaches the proxy.
2. What Didn’t Work (And Why)
During a typical Flutter pentesting engagement, several approaches are commonly attempted and frequently fail:
Failed Attempts:
- NVISO disable-flutter-tls-verification — Pattern-matching script that works on many Flutter versions but fails when the memory pattern differs
- frida-flutterproxy (hackcatml) — Burp proxy helper for Flutter apps
- reFlutter — Automated patching tool that fails with unsupported engine hashes or missing `libapp.so`
– Manual iptables routing — Even with Burp listening on all interfaces and invisible proxying enabled, TLS verification still blocks traffic
Manual routing attempt (still blocked by TLS verification) iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination Burp_IP:Burp_Port
The critical insight is that these scripts often rely on byte-pattern signatures that match specific memory layouts. When the architecture changes, the patterns shift.
3. The Architecture Factor: ARM vs. x86_64
The same scripts that fail on one machine may work perfectly on another. The difference often comes down to the emulator architecture:
| Environment | Architecture | Script Behavior |
|-|–|–|
| macOS AVD | ARM emulator | Scripts work (patterns match) |
| PC AVD | x86_64 emulator | Scripts fail (patterns don’t match) |
Different architectures = different memory layouts and offsets. The scripts’ byte patterns matched on ARM but not on x86_64 because the bytecode and structure were fundamentally different. This is why “it works on my machine” is a dangerous assumption in mobile pentesting.
4. Reverse Engineering libflutter.so with Ghidra
When automated scripts fail, the only reliable path is to open the binary yourself. Here’s the step-by-step manual approach:
Step 1: Extract libflutter.so
Decompile the APK with apktool apktool d target_app.apk -o decompiled_app/ Navigate to the extracted libraries cd decompiled_app/lib/ Choose the architecture that matches your emulator/device For x86_64 emulator: pick x86_64 For ARM device: pick arm64-v8a or armeabi-v7a ls x86_64/libflutter.so
Step 2: Load in Ghidra
- Launch Ghidra (requires Java 22 or higher)
- File → Import → Select `libflutter.so`
– Double-click to analyze (accept default analysis options)
Step 3: Locate the Target Function
Flutter uses BoringSSL. The target function is `ssl_crypto_x509_session_verify_cert_chain` in ssl_x509.cc. This function:
– Takes 3 arguments
– Returns a boolean (true = verified, false = failed)
To find it in Ghidra:
- Search → For Strings → Type “ssl_client” (appears around line 230 in the source)
2. Double-click the result and explore its XREFs
3. Check all XREFs—there may be multiple
- The correct function is the one that takes 3 arguments and returns a boolean
Step 4: Calculate the Offset
Double-click the function name in Ghidra to get the address Example address: 02184644 Subtract the base load address (100000 for x86_64) 02184644 - 100000 = 2084644 The result is the module-relative offset for Frida
The base load address is typically `0x100000` for x86_64 binaries. This offset is what Frida will use to locate the function at runtime.
5. Hooking with Frida
Once you have the correct offset, implement a Frida script to force the return value to `true` (SSL_VERIFY_OK):
// flutter_ssl_bypass.js
// Replace OFFSET with your calculated value
// Replace MODULE_NAME with the target module (usually libflutter.so)
var OFFSET = 0x2084644; // Your calculated offset
var MODULE_NAME = "libflutter.so";
function hook_ssl_verify() {
var module = Process.findModuleByName(MODULE_NAME);
if (!module) {
console.log("[-] Module not found: " + MODULE_NAME);
return;
}
var target_addr = module.base.add(OFFSET);
console.log("[+] Hooking at: " + target_addr);
Interceptor.attach(target_addr, {
onEnter: function(args) {
console.log("[+] ssl_crypto_x509_session_verify_cert_chain called");
},
onLeave: function(retval) {
console.log("[+] Original return value: " + retval);
retval.replace(ptr(1)); // Force return to true (SSL_VERIFY_OK)
console.log("[+] Forced return to: " + retval);
}
});
}
// Wait for the module to load
var checkInterval = setInterval(function() {
if (Process.findModuleByName(MODULE_NAME)) {
clearInterval(checkInterval);
hook_ssl_verify();
}
}, 500);
Running the Script:
Attach to running app frida -U -1 com.example.app -l flutter_ssl_bypass.js Or spawn the app frida -U -f com.example.app -l flutter_ssl_bypass.js --1o-pause
6. Automated Alternatives: PyGhidra and Universal Scripts
For those who prefer automation, tools like `universal-flutter-ssl-pinning` use PyGhidra to perform headless static analysis and automatically generate Frida scripts:
Install requirements pip install pyghidra frida-tools Set Ghidra install directory export GHIDRA_INSTALL_DIR=/path/to/ghidra Analyze libflutter.so and generate scripts python3 flutter_ssl_pinning.py libflutter.so Output files: - flutter_ssl_pinning.js (Frida script) - flutter_ssl_pinning.lua (Renef script) Run with Frida frida -U -f com.example.app -l flutter_ssl_pinning.js
This tool works by:
1. Loading `libflutter.so` headlessly with PyGhidra
2. Scanning for the “ssl_client” string
3. Resolving cross-references to find the 3-parameter function
- Baking the RVA into ready-to-run Frida and Renef scripts
It has been tested on Google Flutter builds, Shorebird-patched Flutter builds, Ghidra 12.x, and Frida 17.x/16.x.
7. Alternative: The Renef Approach
For ARM64 targets, Renef provides an even more elegant solution by patching the function entry directly:
-- flutter_ssl_pinning.lua -- Patches the function entry with MOV X0, 1 ; RET Memory.patch(offset, "MOV X0, 1 ; RET")
Run with:
renef -s com.example.app -l flutter_ssl_pinning.lua
This approach is particularly useful for ARM64 devices where Frida may be detected or unstable.
8. Traffic Interception Setup
Once the SSL verification is bypassed, ensure traffic reaches Burp Suite:
On Android Emulator:
Launch emulator with HTTP proxy emulator -avd YourAVD -http-proxy http://Burp_IP:8083
On Physical Android Device (rooted):
- Use ProxyDroid to force all traffic through Burp
- Configure Burp to listen on all interfaces with invisible proxying enabled
Burp Suite Configuration:
- Proxy → Options → Add a new proxy listener
- Bind to all interfaces (0.0.0.0) on port 8083
- Request Handling → Support Invisible Proxying → True
Result: Burp intercepts ALL HTTPS traffic from the Flutter application. Pinning is fully bypassed.
What Undercode Say:
- Architecture matters more than most pentesters realize. The difference between ARM and x86_64 isn’t just about performance—it fundamentally changes memory layouts and offsets. When a script “should work” but doesn’t, check your architecture first before wasting days on dead ends.
-
Automation is convenient but not infallible. Tools like reFlutter and NVISO scripts work on many targets but fail when Flutter engine versions change or when the app uses non-standard builds. Understanding the manual reverse engineering process is essential for any serious mobile pentester.
-
BoringSSL is the key. Since Flutter uses BoringSSL for all TLS operations, and BoringSSL is open source, you can always trace the verification logic back to the source code. The function `ssl_crypto_x509_session_verify_cert_chain` is your primary target across all Flutter versions.
-
The offset calculation is critical. One off-by-one error in the offset means the hook won’t fire. Always double-check your base address subtraction—for x86_64 it’s
0x100000, but for ARM64 it may be different. -
This technique works across versions. While offsets change with each Flutter engine version, the methodology remains consistent. The function name and signature stay the same because BoringSSL’s API is stable.
Prediction:
-
+1 As Flutter adoption continues to grow in enterprise mobile applications, the demand for mobile pentesters who can manually reverse engineer `libflutter.so` will increase significantly. This skill will become a differentiator in the security job market.
-
+1 Automated tools will evolve to handle architecture detection better, with scripts that dynamically scan for patterns across multiple architectures rather than relying on hardcoded offsets. The `universal-flutter-ssl-pinning` project is already moving in this direction.
-
-1 Flutter’s security team may introduce additional obfuscation or anti-hooking measures in future engine versions, making it harder to locate the verification function through simple string searches. Pentesters will need to adapt with more sophisticated reverse engineering techniques.
-
-1 The rise of Flutter on iOS presents similar challenges, but with additional code signing and anti-debugging protections that make Frida hooks more difficult to deploy. iOS pentesters will face an even steeper learning curve.
-
+1 The open-source nature of BoringSSL ensures that the verification logic will remain traceable. As long as developers can access the source code, determined pentesters will find a way to bypass pinning—it’s a cat-and-mouse game that favors the attacker with sufficient patience and skill.
▶️ Related Video (80% Match):
https://www.youtube.com/watch?v=3DtH1LloBxM
🎯Let’s Practice For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
IT/Security Reporter URL:
Reported By: Zlatanh Flutter – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅


