🧩 Part 21: Reverse Engineering Flutter Apps Using JADX & Ghidra — How Hackers Extract Secrets…
FlutterPulseThis article was translated specially for the channel FlutterPulseYou'll find lots of interesting things related to Flutter on this channel. Don't hesitate to subscribe!🚀

Flutter apps are compiled to native binaries, giving developers a false sense of protection. But in reality, attackers can reverse…
🔙 Check Previous Part [How Hackers Extract Secrets from Your Flutter App's RAM]
Flutter apps are compiled to native binaries, giving developers a false sense of protection. But in reality, attackers can reverse engineer these binaries — especially the .so libraries — to extract:
- Hardcoded secrets
- API keys
- Obfuscated logic
- Hidden debug functions
- Secret URLs or tokens
In this article, we explore how Flutter apps are dissected using tools like JADX, Ghidra, and IDA Pro, with practical methods attackers use — and how you can protect against it.
🧠 Understanding Flutter's Native Output
Flutter compiles Dart code into native machine code during release builds. Your APK includes:
lib/
└── arm64-v8a/
└── libapp.so ← Compiled Dart code
└── libflutter.so ← Flutter engine
classes.dex ← Java/Kotlin glue code
While classes.dex contains mostly boilerplate, the real logic lives inside libapp.so, and that's where attackers target their analysis.
🕵️♂️ Real-World Exploit Scenarios
💣 Exploit 1: Decompiling Java Classes with JADX
JADX converts classes.dex to Java source code:
jadx -d output/ your_app.apk
Even though Dart logic isn't here, attackers use it to:
- Discover method names
- Identify Flutter plugin bridges
- Locate
MethodChannelentrypoints - Find app startup routinesExample:
MethodChannel channel = new MethodChannel(flutterEngine.getDartExecutor(), "com.example.secret");
channel.setMethodCallHandler((call, result) -> {
if (call.method.equals("getToken")) {
result.success("hardcoded_token_xyz");
}
});
🔥 Outcome: Hardcoded secrets found in glue code!
💣 Exploit 2: Static Analysis of libapp.so Using Ghidra or IDA
libapp.so is the real jackpot — attackers load it into Ghidra:
ghidraRun
→ File > Import > libapp.so
From there, they:
- Identify
main()or Dart entrypoints - Trace disassembled logic
- Reverse conditions, loops, and crypto usage
- Dump string tables and look for:
- API endpoints
- Secret constants
- License flags
- Logic toggles
Example string dump reveals:
https://staging-api.example.com/
premiumFeatureUnlocked
SECRET_ENCRYPTION_KEY
🔥 Outcome: Even if obfuscated, critical values are still accessible.
💣 Exploit 3: Extracting Method Names Despite Obfuscation
Even with:
flutter build apk --obfuscate --split-debug-info=build/symbols
Some symbol names in native code or plugin layers may remain intact — especially from plugins or native Java/Kotlin.
Attackers can find things like:
Java_com_example_plugin_SensitiveHelper_getEncryptionKey
🔥 Outcome: Functionality is exposed even in obfuscated apps.
💣 Exploit 4: Reconstructing Logic Flow for Feature Unlocks
Using Ghidra's decompiler and control flow graphs, attackers trace:
- Jump/call/return patterns
- Feature checks
- Boolean toggles
- Conditional blocks
This helps them patch libapp.so using tools like r2 (Radare2), Frida, or hex editors — e.g., flipping a conditional jump to always execute.
🔥 Outcome: Paid features unlocked. Security logic bypassed.
📉 Real-World Impact

Risk Level: CRITICAL
🛡️ How to Fix It
✅ Fix 1: Obfuscate Dart Code and Use Split Debug Info
flutter build apk --obfuscate --split-debug-info=build/symbols
This renames method names, class identifiers, and adds symbol stripping.
✅ Fix 2: Use ProGuard/R8 for Java/Kotlin Bridge Code
Apply code shrinking to Java classes via proguard-rules.pro:
-dontwarn io.flutter.**
-keep class io.flutter.** { *; }
-assumenosideeffects class android.util.Log {
public static *** d(...);
}
This reduces visibility of native bridges and helper methods.
✅ Fix 3: Avoid Hardcoding Secrets Anywhere
Never write secrets in:
- Dart code
- Native C++
- Java glue layers
- Static strings or constants
Instead, fetch secrets securely from a backend at runtime, with authentication.
✅ Fix 4: Move Sensitive Logic to Backend
Logic like:
- Premium checks
- Role-based features
- Licensing
- Data validation
…should be verified on your backend, not just in the client.
✅ Fix 5: Enable Anti-Tampering & App Signature Check
In native code (Java/Kotlin):
PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
Signature[] sigs = info.signingInfo.getApkContentsSigners();
Terminate app if the signature doesn't match the production one.
❌ Anti-Patterns to Avoid

✅ Developer Checklist
- Obfuscate Dart using
--obfuscateand--split-debug-info - Strip symbols from
.sofiles - Use ProGuard/R8 on Java/Kotlin code
- Never hardcode secrets or tokens
- Perform sensitive logic on the server side
- Enable app signature verification and anti-tampering
- Periodically scan your APK for exposed strings
👀 Up Next
🧱Part 22: Flutter App Integrity Bypass via Repack & Side-loading — How Modified APKs Bypass Validation
Thank you for reading this article
If I missed something or made an error, please let me know in the comments. I'm always eager to learn and improve.
Give a clap 👏 if you found this article helpful.