Android Static Analysis
How to use this cheat sheet
This page is built as a working checklist you can keep open while reviewing an APK. It assumes you already know AppSec (web/API or mobile) and want an assessment-grade static workflow: what to extract, what to flag in the manifest, what grep patterns reliably find real bugs, and what to verify in code before calling it a finding.
Static analysis should answer three questions quickly: (1) what can be reached from outside, (2) what inputs control privileged actions/data, and (3) what protections are real vs assumed.
Tooling setup (minimal, practical)
Recommended baseline tools:
- apktool (manifest/resources decoding)
- jadx (Java/Kotlin decompile)
- ripgrep (rg) (fast pattern search)
- keytool / apksigner (signing/cert sanity)
Sanity check versions (optional):
apktool --version
jadx --version
rg --version
APK intake and workspace (repeatable)
Create a clean workspace per target:
APP=app
APK=$APP.apk
mkdir -p runs/$APP
cp "$APK" runs/$APP/
cd runs/$APP
Quick metadata (package name, version, SDKs):
aapt dump badging "$APK" | sed -n '1,40p'
Signature check (release vs debug, key rotation clues):
apksigner verify --print-certs "$APK"
# or:
keytool -printcert -jarfile "$APK" | sed -n '1,80p'
Decode + decompile (two outputs, two purposes)
Decode resources/manifest (best for manifest + XML configs):
apktool d -f "$APK" -o out
Decompile code (best for logic, routing, trust assumptions):
jadx "$APK" -d jadx-out
Use out/AndroidManifest.xml for the canonical decoded manifest (apktool output), and jadx-out/ for code-level verification.
Manifest triage: the high-signal checklist
1) App-wide flags that commonly matter
rg -n "android:debuggable|android:allowBackup|android:usesCleartextTraffic|android:networkSecurityConfig|android:fullBackupContent|android:backupAgent" out/AndroidManifest.xml
- debuggable=true: shipping debug surfaces, logging, relaxed policies (severity depends on environment).
- allowBackup / backupAgent / fullBackupContent: sensitive data in backups is a recurring real-world issue.
- usesCleartextTraffic / networkSecurityConfig: check if cleartext or trust anchors were expanded.
2) Exported components and entry points
rg -n "android:exported=\"true\"" out/AndroidManifest.xml
rg -n "<activity|<service|<receiver|<provider" out/AndroidManifest.xml
For each exported component, capture:
- name + exported flag
- intent-filters (actions/categories/data)
- permission gates (
android:permission) - special flags (task/launch, grantUriPermissions, process)
3) Intent-filters and deep link surfaces
rg -n "<intent-filter|android.intent.action.VIEW|android.intent.category.BROWSABLE|android.intent.category.DEFAULT" out/AndroidManifest.xml
rg -n "<data\s|android:scheme=|android:host=|android:path=|android:pathPrefix=|android:pathPattern=" out/AndroidManifest.xml
4) Permissions model (declared vs enforced)
rg -n "<permission\b|protectionLevel|signature|dangerous|normal" out/AndroidManifest.xml
rg -n "android:permission=\"" out/AndroidManifest.xml
Common mistake: components âprotectedâ by a normal/dangerous permission for a sensitive capability. If itâs truly internal IPC, signature-level permissions or non-exported components are the usual baseline.
5) Content Providers: authorities, permissions, and grants
rg -n "<provider\b|authorities=|grantUriPermissions=|exported=|readPermission=|writePermission=|permission=" out/AndroidManifest.xml
rg -n "FileProvider|androidx.core.content.FileProvider" out/AndroidManifest.xml
6) Queries (Android 11+ package visibility)
rg -n "<queries\b|<package\b|<intent\b" out/AndroidManifest.xml
7) Task/launch quirks (flow confusion indicators)
rg -n "launchMode=|taskAffinity=|allowTaskReparenting=|excludeFromRecents=|clearTaskOnLaunch=|alwaysRetainTaskState=" out/AndroidManifest.xml
Code triage: fast entry-point discovery patterns
These searches are designed to quickly locate where untrusted input is parsed and where privileged actions occur.
1) Activities: intent data + extras parsing
rg -n "getIntent\(|intent\.|getStringExtra\(|getIntExtra\(|getBooleanExtra\(|getParcelableExtra\(|getSerializableExtra\(" jadx-out
rg -n "getData\(|getDataString\(|Uri\.parse\(|getQueryParameter\(" jadx-out
2) Services: onStartCommand + binder
rg -n "onStartCommand\(|startForeground\(|startForegroundService\(|startService\(" jadx-out
rg -n "onBind\(|IBinder|\.Stub\b|AIDL|Binder\.getCallingUid\(|getCallingUid\(" jadx-out
3) Broadcast Receivers: actions + extras
rg -n "class\s+\w+Receiver\b|extends\s+BroadcastReceiver|onReceive\(" jadx-out
rg -n "sendBroadcast\(|sendOrderedBroadcast\(|registerReceiver\(" jadx-out
4) Content Providers: query/insert/update/delete/openFile
rg -n "extends\s+ContentProvider|onCreate\(|query\(|insert\(|update\(|delete\(|openFile\(|openAssetFile\(" jadx-out
rg -n "ContentResolver\.query\(|ContentResolver\.insert\(|ContentResolver\.update\(|ContentResolver\.delete\(" jadx-out
5) Central routers / dispatchers (high ROI)
rg -n "Router|Dispatcher|DeepLink|Navigation|Navigator|Route|IntentHandler" jadx-out
rg -n "\b(target|next|screen|destination|redirect|url|uri|deeplink)\b" jadx-out
Security-sensitive sinks (what to follow after you find input parsing)
When you find a place that reads extras/URI/binder params, follow it to the first meaningful sink. The sink determines exploitability and severity.
Network sinks
rg -n "OkHttpClient|Request\.Builder\(|Retrofit|HttpUrl|URLConnection|WebViewClient" jadx-out
File/storage sinks
rg -n "SharedPreferences|getSharedPreferences\(|MODE_WORLD_READABLE|openFileOutput\(|File\(|FileOutputStream\(|getExternalFilesDir\(|Environment\.getExternalStorage" jadx-out
IPC sinks
rg -n "PendingIntent\.|getActivity\(|getService\(|getBroadcast\(|FLAG_IMMUTABLE|FLAG_MUTABLE" jadx-out
rg -n "startActivity\(|startService\(|bindService\(|setResult\(|setResultExtras\(|setResultData\(" jadx-out
WebView sinks (frequent real findings)
rg -n "WebView\b|setJavaScriptEnabled\(|addJavascriptInterface\(|setAllowFileAccess\(|setAllowUniversalAccessFromFileURLs\(|loadUrl\(|loadDataWithBaseURL\(" jadx-out
Crypto / key material signals (triage, not theory)
rg -n "Cipher\.getInstance\(|SecretKeySpec\(|MessageDigest\(|Mac\.getInstance\(|Base64\.|KeyStore\b|AndroidKeyStore" jadx-out
Logging / telemetry leaks (common in mobile)
rg -n "\bLog\.(d|i|w|e|v)\(|Timber\.|println\(" jadx-out
Component-by-component static checklist (what to confirm before reporting)
Activities (exported / deep link)
- Exported? If yes: does the Activity enforce login/authorization inside lifecycle (not only via UI flow)?
- Does it parse extras/URI to select accounts/objects (
userId/accountId/docId)? - Does it accept navigation params (
next/target/url)? Is it allowlisted? - Does it trigger state changes on entry (sync, payment initiation, settings changes)?
Services (started / bound)
- Exported started service reading extras = command surface. Is it gated by strong permission?
- Bound service: is caller verified (
getCallingUid()) and is method-level authz explicit? - Any debug/admin methods in binder interface?
- Does it perform network/file operations based on caller-controlled params (confused deputy risk)?
- Is it spam-resistant (idempotent / avoids expensive work per call)?
Broadcast Receivers
- Custom actions + exported receiver = forged broadcast candidate. Is permission signature-level?
- Any ordered broadcast result leakage (
setResultExtras/setResultData)? - Dynamic receivers: do they register with a permission? Are actions guessable?
Content Providers
- Exported provider without strict permissions is a red flag. Validate read/write enforcement in code.
- URI permission grants:
grantUriPermissions,FLAG_GRANT_READ_URI_PERMISSIONusage, and path constraints. - FileProvider: verify paths configuration and ensure it doesnât expose unintended files.
- SQL usage inside provider (selection/selectionArgs handling) and path traversal logic in
openFile().
Grep packs (copy/paste bundles)
Manifest: exported + deep links + permissions
rg -n "android:exported=\"true\"|<intent-filter|BROWSABLE|action\.VIEW|android:scheme=|android:host=|android:path|android:permission=|<permission\b|protectionLevel|grantUriPermissions|authorities=" out/AndroidManifest.xml
Entry parsing (extras/URI) + routing keywords
rg -n "getStringExtra\(|getIntExtra\(|getBooleanExtra\(|getParcelableExtra\(|getSerializableExtra\(|getData\(|getDataString\(|getQueryParameter\(|Uri\.parse\(" jadx-out
rg -n "\b(next|target|screen|route|redirect|url|uri|deeplink|destination)\b" jadx-out
IPC + permission enforcement signals
rg -n "onBind\(|\.Stub\b|AIDL|Binder\.getCallingUid\(|getCallingUid\(|enforceCallingPermission\(|checkCallingPermission\(" jadx-out
rg -n "PendingIntent\.|FLAG_IMMUTABLE|FLAG_MUTABLE|sendBroadcast\(|sendOrderedBroadcast\(|registerReceiver\(" jadx-out
Storage + secrets + logging
rg -n "SharedPreferences|getSharedPreferences\(|openFileOutput\(|MODE_WORLD_|getExternalFilesDir\(|Environment\.getExternalStorage|KeyStore\b|AndroidKeyStore|Base64\.|Cipher\.getInstance\(|SecretKeySpec\(|\bLog\." jadx-out
WebView risk pack
rg -n "WebView\b|addJavascriptInterface\(|setJavaScriptEnabled\(|setAllowFileAccess\(|setAllowUniversalAccessFromFileURLs\(|loadUrl\(|loadDataWithBaseURL\(" jadx-out
These packs are triage-oriented: they tell you where to look. Severity depends on reachable surface and what the code does with the input.
Interview questions & answers (Easy â Medium â Hard â Senior)
Easy
-
Whatâs your first step when you receive an APK for assessment?
I build an attack-surface map: decode the manifest, enumerate exported components and intent-filters, then identify where intents/extras/URIs/binder params are parsed and what privileged actions they control.
-
Why is
AndroidManifest.xmlhigh-signal?It declares externally reachable entry points (exported components, deep links), permission gates, and app-wide security posture flags (backup, cleartext, network security config).
Medium
-
How do you prioritize static review in a large app?
Exported + parameter-driven entry points first: Activities with BROWSABLE filters, receivers with custom actions, services reading extras, and any binder interfaces. Then I follow input parsing to network/file/IPC sinks.
-
Whatâs a common false positive in static analysis?
Flagging a scary sink without proving reachability. Static triage must connect: entry point â attacker-controlled input â privileged sink, and validate that any âpermission checksâ are real and enforceable.
Hard
-
How do you validate authorization for binder methods statically?
I check whether the service is exported and whether binding is permission-gated. Then in code I look for caller identity checks (
getCallingUid()), enforcement APIs (enforceCallingPermission), and method-level authorization based on user/session context. -
What static signals suggest flow-confusion risks?
Broad deep link filters (generic paths), router Activities using
next/target/url, and task/launch flags like customtaskAffinityor non-standardlaunchMode. Then I verify whether post-login intent reuse is sanitized.
Senior
-
What does âstatic analysis done wellâ look like in a report?
Itâs not a dump of grep hits. Itâs a chain: the component is reachable (manifest evidence), the app trusts a parameter (code evidence), the parameter controls a privileged action/data access (sink evidence), and the fix is explicit (reduce export, tighten permissions, add caller/session binding, allowlist inputs).
-
How do you avoid missing hidden entry points?
I treat exported components like public endpoints and search for centralized routing/dispatch, intent builders, and IPC glue. Those are where apps accidentally expose privileged actions under âhelperâ code.