What is reverse engineering? How to reverse engineer any app in Flutter? How to protect against reverse engineering in Flutter apps?
Answer
What is Reverse Engineering?
Reverse engineering is the process of analyzing a compiled application to understand its internal logic, extract source code, assets, API keys, or business logic — without access to the original source code.
In mobile apps, reverse engineers use tools to decompile APKs/IPAs, inspect network traffic, analyze binary code, and extract sensitive data.
How to Reverse Engineer a Flutter App
Flutter compiles to native ARM code (AOT compilation), making it harder to reverse engineer than React Native or Cordova apps — but not impossible.
Step 1: Extract the APK
bashadb shell pm path com.example.app adb pull /data/app/com.example.app.apk
Step 2: Decompile with APKTool
bashapktool d app.apk -o output_folder
This extracts:
- — permissions, activities, deep linkstext
AndroidManifest.xml - — layouts, drawables, stringstext
res/ - — Dalvik bytecode (wrapper code only for Flutter)text
smali/ - — nativetext
lib/files (Flutter engine + app code)text.so
Step 3: Analyze the Flutter text.so
Binary
.soFlutter app logic lives in
libapp.sobash# Blutter: Flutter reverse engineering tool git clone https://github.com/worawit/blutter python3 setup.py com.example.app/lib/arm64-v8a/libapp.so output/
Blutter can reconstruct Dart class names, method names, and field names from the snapshot.
Step 4: Inspect Network Traffic
Use a proxy like mitmproxy or Charles Proxy to intercept API calls:
bashmitmproxy --mode transparent
This exposes API endpoints, request/response payloads, and auth tokens (if certificate pinning is not implemented).
Step 5: Extract Hardcoded Secrets
Use the
stringsbashstrings libapp.so | grep -i "api_key\|secret\|token\|password"
How to Protect Flutter Apps from Reverse Engineering
1. Enable Code Obfuscation (Most Important)
Flutter supports Dart obfuscation during release builds:
bashflutter build apk --obfuscate --split-debug-info=./debug-info
This renames classes, methods, and fields to meaningless symbols, making tools like Blutter produce unreadable output.
Important: Save the
directory — you need it to decode crash stack traces in production.textdebug-info
2. Never Hardcode Secrets
dart// ❌ Wrong — easily extracted from binary const String apiKey = 'sk-abc123...'; // ✅ Better — use environment variables at build time const String apiKey = String.fromEnvironment('API_KEY');
Build with:
bashflutter build apk --dart-define=API_KEY=sk-abc123...
Note: For truly sensitive keys, use a backend proxy —
values can still be extracted from the binary by a skilled attacker.text--dart-define
3. Implement Certificate Pinning
Prevent traffic interception by pinning your server's SSL certificate:
dartimport 'package:dio/dio.dart'; import 'dart:io'; final dio = Dio(); (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () { final client = HttpClient(); client.badCertificateCallback = (cert, host, port) { // Validate against your pinned certificate fingerprint return _isPinnedCertificate(cert); }; return client; };
4. Root / Jailbreak Detection
dartimport 'package:flutter_jailbreak_detection/flutter_jailbreak_detection.dart'; Future<void> checkDeviceSecurity() async { bool isJailbroken = await FlutterJailbreakDetection.jailbroken; bool isDeveloperMode = await FlutterJailbreakDetection.developerMode; if (isJailbroken || isDeveloperMode) { // Block access or warn user showSecurityWarningDialog(); } }
5. Tamper Detection (Signature Verification)
Verify your APK signature at runtime to detect repackaged apps:
dartimport 'package:package_info_plus/package_info_plus.dart'; Future<bool> isAppTampered() async { final info = await PackageInfo.fromPlatform(); // Compare against your expected package name return info.packageName != 'com.yourcompany.app'; }
6. Enable ProGuard / R8 for Android
Minify and obfuscate the Java/Kotlin wrapper layer in
android/app/build.gradlegroovybuildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }
7. Use Secure Storage for Sensitive Data
dartimport 'package:flutter_secure_storage/flutter_secure_storage.dart'; const storage = FlutterSecureStorage(); // Store securely using Android Keystore / iOS Keychain await storage.write(key: 'auth_token', value: token); // Retrieve final token = await storage.read(key: 'auth_token');
Protection Techniques Summary
| Technique | Protects Against | Difficulty |
|---|---|---|
text | Code analysis, Blutter | Easy |
| No hardcoded secrets | String extraction | Easy |
text | Accidental exposure | Easy |
| ProGuard / R8 | Java/Kotlin layer analysis | Easy |
| Secure storage | Data extraction | Easy |
| Certificate pinning | MITM, traffic sniffing | Medium |
| Root/jailbreak detection | Privileged attacks | Medium |
| Tamper detection | APK repackaging | Medium |
| Backend proxy for secrets | Complete key exposure | Medium |
Key Takeaway: Security is layered. No single technique is foolproof — a determined attacker with enough time can reverse engineer any app. Your goal is to raise the cost of attack high enough that it's not worth the effort.
Learn more at OWASP Mobile Security and Flutter Obfuscation Docs.