The Android engagement started the way most do: a banking app, a hardened build, a confident dev team. Forty minutes later the tester had pulled session JWTs out of SharedPreferences, dumped a hardcoded API signing key from a native library, and bypassed certificate pinning with a fifteen-line Frida script. The device was a stock Pixel running a publicly available Magisk build. Android pentesting in 2026 still rewards the methodical: enumerate the attack surface, instrument the runtime, intercept the traffic, and read the storage. This guide walks through the workflow end to end with the tooling and commands actually used on engagements.
Setting Up the Lab
Three viable lab options in 2026: a rooted physical device with Magisk (best fidelity, especially for native code and hardware-backed keystore tests), Genymotion or Android Studio AVD with a rooted system image (fast, snapshottable, free for personal use), or Corellium Android for ARM-accurate virtualization when you need a clean device per engagement. Whatever you pick, you need root, you need frida-server, and you need a writable /system for installing the Burp CA as a system trust anchor.
# Workstation toolchain brew install android-platform-tools apktool pip3 install frida-tools objection # Drozer, jadx, MobSF can run in Docker # Verify ADB sees the device adb devices # List of devices attached # emulator-5554 device # Push frida-server matching your frida-tools version adb push frida-server-16.x-android-arm64 /data/local/tmp/ adb shell "su -c 'chmod 755 /data/local/tmp/frida-server-16.x-android-arm64 && /data/local/tmp/frida-server-16.x-android-arm64 &'" # Confirm Frida sees apps frida-ps -Uai
Step 1: Acquiring and Decompiling the APK
Unlike iOS, Android binaries are not encrypted at rest — the APK is a ZIP archive containing classes.dex, resources.arsc, native .so libraries, and the manifest. The first move on every engagement is to pull the APK from the device, unpack it, and skim the manifest for exported components, permissions, and obvious red flags.
# Find the APK path on-device adb shell pm path com.target.app # package:/data/app/~~hash==/com.target.app-hash==/base.apk # Pull it to the workstation adb pull /data/app/~~.../base.apk ./target.apk # Decode resources, manifest, smali apktool d target.apk -o target_decoded/ # target_decoded/AndroidManifest.xml -> attack-surface map # target_decoded/smali/ -> disassembled bytecode # Decompile to Java for readability jadx -d target_jadx target.apk # target_jadx/sources/com/target/... -> readable Java # Search for: api_key, password, token, secret, http:// # Quick triage of the manifest — exported components are entry points grep -E 'android:exported="true"' target_decoded/AndroidManifest.xml
Step 2: Static Analysis
Static analysis on Android is mostly grep, jadx, and a competent SAST tool. MobSF remains the fastest first pass for finding hardcoded secrets, weak crypto configurations, manifest misconfigurations, and outdated dependencies with known CVEs. Beyond that, the highest-yield activity is reading the decompiled login flow, the network layer, and any custom crypto.
# MobSF — static + dynamic in one tool docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest # Drop the APK in the web UI — SBOM, secrets, manifest analysis, Trackers report # Hardcoded secrets sweep grep -rEi "api[_-]?key|secret|password|bearer|aws_access" target_jadx/sources/ # Native libraries — do not ignore these file target_decoded/lib/arm64-v8a/*.so strings target_decoded/lib/arm64-v8a/libtarget.so | grep -Ei "https?://|api" # For deeper analysis: Ghidra / IDA on the .so # Network Security Config cat target_decoded/res/xml/network_security_config.xml # <base-config cleartextTrafficPermitted="true"> -> HTTP allowed # <trust-anchors><certificates src="user"/> -> user CAs trusted (Burp works without bypass) # Build flags grep -i "debuggable\\|allowBackup" target_decoded/AndroidManifest.xml # debuggable=true -> attach a debugger and dump runtime state
Step 3: Dynamic Analysis with Frida, Objection, and Drozer
Frida and Objection cover ninety percent of runtime work. Drozer is the older but still useful framework for IPC and exported-component fuzzing — it can enumerate every content provider, broadcast receiver, and exported activity on the device and probe each one for unauthenticated access.
# Spawn the app under Objection
objection -g com.target.app explore
# Inside the Objection REPL
android root simulate # spoof root status to defeat detection
android sslpinning disable # generic pinning bypass
android hooking list classes # enumerate loaded classes
android hooking watch class com.target.app.LoginActivity
android hooking watch class_method com.target.app.LoginActivity.submit --dump-args --dump-return
android intent launch_activity com.target.app/.AdminActivity
android shell_exec "id" # arbitrary shell command via app context
# Drozer — IPC attack-surface discovery
adb forward tcp:31415 tcp:31415
drozer console connect
dz> run app.package.attacksurface com.target.app
dz> run app.activity.info -a com.target.app
dz> run app.provider.finduri com.target.app
dz> run app.provider.query content://com.target.app.provider/users/
dz> run scanner.provider.injection -a com.target.app # SQLi in providers
# Custom Frida hook — dump arguments to a sensitive method
frida -U -f com.target.app -l hook.js --no-pause
# hook.js
# Java.perform(function() {
# var Login = Java.use('com.target.app.LoginManager');
# Login.authenticate.implementation = function(u, p) {
# console.log('[+] authenticate(' + u + ', ' + p + ')');
# return this.authenticate(u, p);
# };
# });
Step 4: Network Interception and Pinning Bypass
Android 7+ ignores user-installed CAs by default, which means a vanilla Burp setup will fail until you either install the Burp CA as a system trust anchor (requires writable /system) or bypass pinning at runtime with Frida or Objection. Modern apps often pin twice — once via the OkHttp CertificatePinner and once via the platform NetworkSecurityConfig — so a generic bypass that only patches one layer will not work.
# 1) Configure proxy on device
adb shell settings put global http_proxy 192.168.1.10:8080
# 2) Install Burp CA system-wide (rooted device)
openssl x509 -inform DER -in cacert.der -out cacert.pem
HASH=$(openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1)
mv cacert.pem ${HASH}.0
adb push ${HASH}.0 /sdcard/
adb shell "su -c 'mount -o rw,remount /system && cp /sdcard/${HASH}.0 /system/etc/security/cacerts/ && chmod 644 /system/etc/security/cacerts/${HASH}.0'"
# Or use the Magisk module: MagiskTrustUserCerts (no /system writes needed)
# 3) For pinned apps — universal pinning bypass
frida -U -f com.target.app -l universal-android-ssl-bypass.js --no-pause
# Hooks: OkHttp CertificatePinner.check, TrustManagerImpl.verifyChain,
# X509TrustManager.checkServerTrusted, javax.net.ssl.SSLContext.init,
# Conscrypt PlatformAffectedTrustManager, Network Security Config
# 4) Verify in Burp — you should see decrypted requests
# Look for: bearer tokens, JWTs in Authorization, session IDs in body,
# unsigned APIs, GraphQL introspection, debug endpoints (/debug, /admin, /__test)
Step 5: Insecure Local Storage
Android sandboxes app data in /data/data/<package>/, but on a rooted device that sandbox is transparent. The same is true on a stolen unlocked device, on a device with backup enabled, and inside any app with the READ_EXTERNAL_STORAGE permission. Developers regularly persist JWTs in SharedPreferences as plaintext, write SQLite databases without SQLCipher, and dump sensitive cache files into world-readable external storage.
# Browse the app sandbox (rooted device) adb shell "su -c 'cd /data/data/com.target.app && ls -laR'" # SharedPreferences — always plaintext XML adb shell "su -c 'cat /data/data/com.target.app/shared_prefs/auth.xml'" # <string name="auth_token">eyJhbGciOiJIUzI1NiIs...</string> # SQLite databases adb pull /data/data/com.target.app/databases/main.db sqlite3 main.db ".tables" sqlite3 main.db "SELECT * FROM users;" # Encrypted? Run: file main.db — if SQLite format 3, it is NOT encrypted # External storage adb shell "ls /sdcard/Android/data/com.target.app/" # adb backup (legacy but still works on many apps) adb backup -f backup.ab -noapk com.target.app dd if=backup.ab bs=24 skip=1 | zlib-flate -uncompress | tar -xv
Step 6: Exported Components and IPC Abuse
Activities, services, broadcast receivers, and content providers marked android:exported="true" can be invoked by any other app on the device unless they enforce permissions or signature checks. This is the most common authorization-bypass vector in Android: an admin activity that should be reachable only from inside the app turns out to be launchable directly via am start.
# Enumerate exported components from the manifest xmllint --xpath "//activity[@android:exported='true']/@android:name" target_decoded/AndroidManifest.xml # Launch an exported activity directly — bypasses any in-app navigation guards adb shell am start -n com.target.app/.AdminActivity --es "user_id" "1" --ez "is_admin" true # Send a crafted intent to an exported broadcast receiver adb shell am broadcast -a com.target.app.ACTION_RESET_PIN --es "new_pin" "0000" # Query an exported content provider (often vulnerable to SQLi) adb shell content query --uri "content://com.target.app.provider/users" adb shell content query --uri "content://com.target.app.provider/users/1' OR '1'='1" # Deep-link / URL scheme abuse — same idea, triggered from a malicious web page adb shell am start -W -a android.intent.action.VIEW -d "targetapp://transfer?to=attacker&amount=5000" com.target.app
Step 7: WebView and Native Code
WebView is where mobile XSS becomes RCE. setJavaScriptEnabled(true) combined with addJavascriptInterface() exposes Java methods to whatever HTML loads in the WebView — and on older targetSdk values, every public method on the bridged object becomes callable, including reflective access to Runtime.exec(). Native libraries are the other deep area: JNI calls into .so files often hold the most sensitive logic (signing, license checks, custom crypto) precisely because developers think native code is unreadable. It is not.
# WebView audit (in jadx)
# Look for:
# webView.getSettings().setJavaScriptEnabled(true)
# webView.addJavascriptInterface(new Bridge(), "AndroidBridge")
# webView.loadUrl(intent.getStringExtra("url")) -> open redirect / file:// access
# webView.getSettings().setAllowFileAccess(true) -> file:// from JS
# Native library reversing
# 1) jadx finds the JNI binding:
# public native String signRequest(String body);
# 2) Open lib/arm64-v8a/libtarget.so in Ghidra
# 3) Search for Java_com_target_app_Crypto_signRequest
# 4) Read the algorithm — frequently a hardcoded HMAC secret
# Frida hook on a JNI function (no Java side, native directly)
frida -U -n com.target.app -l native-hook.js
Step 8: Reporting
Map every finding to OWASP MASVS 2.0 control IDs (MSTG-STORAGE, MSTG-CRYPTO, MSTG-NETWORK, MSTG-PLATFORM, MSTG-CODE) so the dev team can find prescriptive guidance. Every issue should include the exact tooling and command sequence, the affected class or component, the captured request or dumped artifact as proof, and a remediation that points to a concrete API — EncryptedSharedPreferences instead of SharedPreferences, the Android Keystore with setUserAuthenticationRequired(true) for tokens, the OkHttp CertificatePinner at the network layer, and android:exported="false" with explicit permission checks on every component that does not need cross-app access.
The Toolchain at a Glance
For a typical engagement: a rooted Pixel or AVD with Magisk, ADB and apktool and jadx for decompilation, MobSF for the static first pass, Frida and Objection for runtime instrumentation, Drozer for IPC enumeration, Burp Suite Pro plus Frida pinning bypass for traffic, and Ghidra for the native libraries that the developers thought no one would read. Every tool except Burp Pro is free and open source. The findings that consistently land in the high-severity column are insecure storage, broken pinning, exported components without authorization, and JNI methods with hardcoded keys — same four categories, year after year, regardless of how hardened the build looks on day one.