🧩 Part 21: Reverse Engineering Flutter Apps Using JADX & Ghidra — How Hackers Extract Secrets…

🧩 Part 21: Reverse Engineering Flutter Apps Using JADX & Ghidra — How Hackers Extract Secrets…

FlutterPulse

This 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 MethodChannel entrypoints
  • 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 --obfuscate and --split-debug-info
  • Strip symbols from .so files
  • 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.

Report Page