
Reverse Engineering Android Apps for Hardcoded Secrets and Insecure Storage
A hands-on walkthrough of decompiling an APK, hunting for embedded credentials, and finding exported components that leak more than they should.
Security research and platform engineering at HackerSavanna.
Every Android APK you can install, you can also open. There's no equivalent of a server you can't reach or source code you'll never see. The app is sitting right there on the device, and with a handful of free tools you can walk through its logic, its strings, and very often its secrets, in an afternoon. This is a practical walkthrough of that process, aimed at finding real, reportable findings rather than just poking around.
Getting the APK
If the app is on the Play Store, pull it directly from a device you control with adb:
1adb shell pm list packages | grep target2adb shell pm path com.target.app3adb pull /data/app/~~xxxx==/com.target.app-yyyy==/base.apkNo rooted device needed for this step. If you only have the Play Store listing, tools like apkeep or a browser-based APK extractor work too, as long as you're staying within the scope and rules of the program you're testing against.
Decompiling
Two tools do most of the heavy lifting:
- jadx decompiles the DEX bytecode back into readable, close-to-original Java. It's the fastest way to actually read the app's logic.
- apktool decodes resources and the manifest without fully decompiling code, useful for inspecting
AndroidManifest.xml, exported components, and raw resource files.
1jadx -d output_dir base.apk2apktool d base.apk -o decoded_dirOnce decompiled, output_dir is a full Java source tree you can grep, and decoded_dir/res/values/strings.xml plus any raw assets/ folders are worth a manual pass on their own.
Hunting for hardcoded secrets
This is the single highest-value, lowest-effort step, and it's astonishing how often it pays off. Grep the decompiled source and resources for anything that looks like a credential:
1grep -rniE "(api[_-]?key|secret|token|password|aws_access|firebase)" output_dir/ | grep -v "R.string\|R.id"Common places real secrets end up baked into a shipped APK:
- Firebase configuration with an overly permissive database or Firestore setup (this alone is worth checking against the app's actual backend rules, not just flagging the key)
- Third-party API keys for services like Stripe, Mapbox, or push notification providers, sometimes with production-scope permissions that should have been server-side only
- OAuth client secrets that were never meant to leave a backend
- Hardcoded encryption keys used for "obfuscating" locally stored data, which defeats the purpose entirely once the key ships in the same package as the ciphertext
A key embedded in client code is not a secret. It's a string that happens to be inconvenient to read. Anyone can extract it the same way you just did.
Checking for insecure local storage
Beyond static strings, install the app on an emulator or test device and inspect what it writes to disk:
1adb shell run-as com.target.app ls -la /data/data/com.target.app/shared_prefs/2adb shell run-as com.target.app cat /data/data/com.target.app/shared_prefs/auth.xmlSharedPreferences files are plain XML by default. Auth tokens, session identifiers, or even plaintext passwords stored here are a common finding, particularly in apps that implement "remember me" functionality without using Android's EncryptedSharedPreferences or the Keystore system.
Exported components worth checking
AndroidManifest.xml declares which Activities, Services, Broadcast Receivers, and Content Providers are exported, meaning any other app on the device can interact with them.
1<activity android:name=".DeepLinkHandler"2 android:exported="true">3 <intent-filter>4 <action android:name="android.intent.action.VIEW" />5 <data android:scheme="targetapp" />6 </intent-filter>7</activity>An exported Activity that handles sensitive actions (password reset, payment confirmation, account linking) without validating the calling package or the intent's origin can often be triggered by a malicious app installed on the same device. Content Providers are worth special attention: an exported provider with a broad <path-permission> or none at all can expose a queryable path straight into the app's local database.
Putting it together in a report
A hardcoded secret finding is only as strong as its demonstrated impact. "I found an API key in the APK" is a weak report on its own. "I found an API key in the APK, and used it to read/write data belonging to other users of the production backend" is a critical one. Before submitting:
- Confirm the key or token is live and scoped against production, not a defunct staging environment.
- Demonstrate the actual capability it grants, with a proof of concept request and response.
- Note whether the key is shared across all installs (worse) or per-user (still bad, but different blast radius).
- Include the exact file path or code location where you found it, so the team can trace how it got there and rotate it.
Mobile apps get less scrutiny than web endpoints on most programs, purely because static analysis takes a bit more tooling to set up. That gap is exactly where the impactful findings are still sitting.
Related Posts

Cloud Storage Misconfigurations: Hunting Exposed S3 and GCS Buckets
A field guide to discovering, safely confirming, and accurately assessing impact on publicly exposed object storage across every major cloud provider.

IDOR Hunting at Scale: From Manual Testing to Automated Discovery
A systematic methodology for finding broken object-level authorization across every method, endpoint, and hidden reference, not just the obvious ones.