Most mobile penetration testing tools are built for Java/Kotlin (Android) or Swift/Objective-C (iOS). When pentesters encounter a Flutter application, they often hit a wall. Automated scanners fail. Standard proxy tools capture nothing. Decompilers produce unreadable assembly. This creates a false sense of security for engineering teams who assume their Dart code is safe from reverse engineering. It is not. We decompile, hook, and manipulate Flutter applications at the binary level.
The native binary challenge: Analyzing compiled Dart
Unlike React Native, which bundles readable JavaScript files, Flutter compiles Dart code directly into a native library (usually libapp.so on Android and the `App` framework on iOS). If you try to decompile a Flutter APK with standard tools like JADX or Apktool, you will only see the thin native Java wrapper code, not your actual application logic.
To analyze the application, we must attack the Dart Virtual Machine snapshot. We use specialized tooling like reFlutter, darter, or Ghidra with Dart-specific plugins. The reFlutter tool actively patches the Flutter engine library (libflutter.so) inside the APK, recompiling it to expose internal tracking. This allows us to inspect the Dart class structure, extract hardcoded strings, and map all API endpoints.
Once we identify the memory offsets for key functions (like authentication checks or encryption routines), we use Frida scripts to hook into the runtime dynamically. This allows us to view variable states, alter function return values in real-time, and extract cryptographic keys directly from memory before they are used.
Defeating Flutter SSL Pinning (BoringSSL Bypass)
Standard mobile applications use the host operating system's network stack for HTTPS. Flutter does not. Flutter compiles its own HTTP library (BoringSSL) directly into the engine. Because of this architectural choice, standard Android proxy settings, system trust certificates, and tools like `apk-mitm` or standard Frida modules (like `objection`) are completely ignored by the app. The traffic remains encrypted and invisible.
To capture the network traffic, we must attack BoringSSL directly. We use custom Frida scripts that target the BoringSSL `ssl_verify_peer_cert` or `ssl_crypto_x509_session_verify_cert_chain` validation offsets within the compiled engine. Because these offsets change with every single Flutter SDK release, we dynamically search the memory space for the verification function signatures. By patching these functions at runtime to always return `true` (success), we force the application to accept our Burp Suite proxy certificate.
// Example Frida logic to bypass BoringSSL check in Flutter (simplified conceptual script)
// We find the offset for the certificate verification function based on the Flutter SDK version
var target_function_offset = 0x1a2b3c;
var base_addr = Module.getBaseAddress("libflutter.so");
Interceptor.replace(base_addr.add(target_function_offset), new NativeCallback(function (custom_ssl, custom_out_alert) {
console.log("[+] BoringSSL Certificate Verify Bypass Triggered");
// 0 usually indicates success in the specific BoringSSL implementation used by Dart
return 0;
}, 'int', ['pointer', 'pointer'])); Once SSL pinning is defeated, we intercept every API call. We test for Broken Object Level Authorization (BOLA), mass assignment, and injection vulnerabilities exactly as we would for a standard web application API.
Frida hook bypasses local PIN validation in a Flutter crypto wallet
On a Flutter-based mobile wallet app, the transaction approval PIN was checked locally before triggering the withdrawal API. We used a Frida script to hook the `verifyPin` method inside libapp.so. By replacing the native return value with a hardcoded boolean true, we bypassed the local PIN entry screen entirely and authorized massive outbound payments without ever knowing the user's secret code. The backend API trusted the client implicitly.
Attacking the MethodChannel Boundary
Flutter applications frequently need to communicate with native OS features (like the camera, Bluetooth, or biometric sensors). They do this using `MethodChannels`. The Dart code sends a message across the boundary, and the native Kotlin or Swift code processes it and returns a result.
This boundary is a prime target for exploitation. We inspect the method channels that connect Dart to Android or iOS. We craft custom intents or write Frida scripts to send malformed data directly into the native handlers: missing fields, wrong types, excessively long strings, local file paths, and calls executed from the wrong app state (e.g., calling the biometric success callback before authentication even starts). Native handlers require the exact same rigorous input validation and permission checks as a public backend API endpoint.
Exploiting Insecure Local Storage
Flutter developers often use packages like `shared_preferences` or `hive` to store data locally. These packages store data in plaintext XML or JSON files within the application's data directory. If a device is rooted (or if an attacker gains arbitrary file read via a separate vulnerability), this data is immediately compromised.
We audit the local storage implementation to ensure all sensitive data (session tokens, PII, offline balances) is stored using cryptographically backed systems, specifically `flutter_secure_storage`. This package utilizes the Android Keystore and the iOS Keychain, heavily mitigating data extraction risks even on compromised devices.
Extracting Cryptographic Secrets from Dart Memory
Many Flutter applications attempt to encrypt data locally before sending it to the server, or implement complex request signing (HMAC) to prevent tampering. Developers often mistakenly hardcode the encryption keys directly inside the Dart source code, assuming the native binary compilation protects them.
We do not need to reverse-engineer the exact encryption algorithm. Instead, we dump the Dart VM memory space while the application is running. Because Dart garbage collection operates differently than standard native memory, strings and cryptographic keys often remain resident in RAM far longer than intended. We use advanced memory scanning scripts to dump the heap, searching for high-entropy strings, JWT patterns, or specific lengths of base64-encoded keys. Once we extract the HMAC secret key directly from the live memory dump, we can forge valid request signatures indefinitely, completely bypassing the application's payload tampering protections.
Securing your Flutter application
To protect your Flutter application from dynamic analysis and logic abuse, you must implement these controls:
- Use cryptographically backed storage: Store all user secrets in the secure systems (Keychain/KeyStore) using verified libraries. Never use `shared_preferences` for tokens.
- Implement strict server-side verification: The client must never make authorization decisions. The server must check the user's role, balance, and permissions for every single transaction.
- Use RASP (Runtime Application Self-Protection) controls: Detect if the application is running on a rooted device, if a debugger (like Frida or LLDB) is attached, or if the app is running in an emulator. Terminate the app if these conditions are met.
- Obfuscate Dart code: Always build with the `--obfuscate` and `--split-debug-info` flags. This strips function names and symbols, increasing the time required for an attacker to reverse engineer the binary logic.
Keep evidence that survives retesting
A quality penetration test report must record the exact Flutter version, the app hash, the device state, the specific hook target offset, the original return value, the changed value, the intercepted request, the API response, and the backend server record. We retain symbols or offsets specifically for the tested build. After your team pushes a fix, we replay the exact same action on a new release build to definitively confirm the server rejects the bypassed client state.
Use our Flutter security checklist for storage and release settings, then review our comprehensive approach to mobile application penetration testing.
Get your Flutter app audited
Flutter apps are incredibly complex to audit because of their native compilation and custom network stacks. Automated scanners will miss almost all binary vulnerabilities, BoringSSL bypasses, and business logic flaws. You need manual, adversarial penetration testing.
At Simpa Labs, we specialize in reverse-engineering mobile binaries in Nigeria and globally. We will bypass your pinning, hook your Dart runtime, check your APIs, test your local storage, and audit your mobile architecture to make sure it is genuinely secure against determined attackers.