This module teaches you how to inspect an Android APK without running it: how to extract meaningful artifacts, read decompiled code, identify where security-relevant logic lives (attestation, pinning, root checks, crypto), analyze native libraries, recognize obfuscation/anti-tamper, and produce an evidence-backed static-analysis report that drives safe follow-ups in dynamic testing or vendor validation. All commands and examples below are intended for authorized lab work only on test APKs or apps inside your written scope.
3.0 Learning objectives
After this module you will be able to:
- Unpack and decompile an APK into readable artifacts (Manifest, resources, Java/Kotlin pseudo-code, native libs).
- Locate security-critical code paths (auth, crypto, root checks, attestation, certificate pinning).
- Identify and characterize native libraries (
.so) and determine whether they contain symbols or look stripped. - Detect obfuscation / string-encryption / tamper-resistance patterns and estimate effort to reverse.
- Produce a prioritized static-analysis finding list (file, line, justification, severity, next-action).
3.1 Prepare: tools you will use (lab)
Install/use these tools in your lab environment:
apktool— decode resources & manifest, rebuild.jadx/jadx-gui— DEX → Java/Kotlin decompilation (interactive is very useful).dex2jar+jd-gui— alternative Java decompilation pipeline.aapt/aapt2— quick APK metadata.apksigner/keytool/openssl— inspect signing certs.strings,grep,xmllint,jq— text extraction & lightweight parsing.readelf,nm,objdump,file— native ELF inspection.Ghidra,radare2/Cutter, orIDA— deep native reverse engineering (GUI + analysis).MobSF— optional automated/static triage for large numbers of APKs.python/ small scripts — automate repetitive checks and produce JSON output.
3.2 Unpack & baseline inspection (commands + what to record)
Start by creating a working directory for the APK and recording checksums.
mkdir -p ~/lab/static_analysis/appA
cd ~/lab/static_analysis/appA
cp /path/to/app.apk .
sha256sum app.apk > app.apk.sha256
Quick metadata & manifest:
# Metadata
aapt dump badging app.apk > metadata.txt
# Signing certificates
apksigner verify --print-certs app.apk > apksigner.txt
# List files inside APK
unzip -l app.apk > filelist.txt
# Decode resources & manifest for readable XML
apktool d app.apk -o app_unpacked
Record (in your notes or JSON manifest):
package name,versionName,versionCode(frommetadata.txt)targetSdkVersionandminSdkVersion- Signing cert SHA-1 / SHA-256 (from
apksigner.txt) — important for attestation contexts - Presence of
lib/,assets/, number of.dexfiles,resources.arscsize,AndroidManifest.xmlflags (allowBackup, exported components,sharedUserId)
3.3 Manifest analysis — what to inspect and why
Open app_unpacked/AndroidManifest.xml and look for:
android:exported="true"components — list them and note whether they require a permission. Exported components are high-value in static testing.<provider>entries — noteauthoritiesand whetherandroid:grantUriPermissionsis set. ContentProviders are a frequent data-leak source.android:allowBackup="true"— combined with sensitive data storage it becomes a clear risk.uses-permissionlist — flag dangerous permissions (SMS, CONTACTS, STORAGE) and permissions that look excessive.- Signature-related restrictions (e.g.,
protectionLevel="signature"or custom permission usage).
Record line and file references for each finding so you can point to evidence in the report.
3.4 Searching for endpoints, secrets and suspicious strings (commands + heuristics)
Search both the raw APK and the unpacked resources. Use broad patterns and then refine.
# quick string search inside APK (fast)
strings app.apk | egrep -i "http|https|://|api|endpoint|token|key|secret|client|bearer" | sort -u > apk_strings.txt
# more precise search in unpacked resources
grep -R --line-number -iE "http[s]?://|api|token|key|secret|client|bearer|password|username" app_unpacked/ > resource_hits.txt
# search for crypto / keystore usage
grep -R --line-number -iE "KeyStore|KeyPairGenerator|KeyGenParameterSpec|AndroidKeyStore|Cipher|SecretKey|MessageDigest|Signature" app_unpacked/ > crypto_hits.txt
Heuristics:
- Full URLs or base URLs in
strings→ record hostnames and endpoints. - Short-looking hex strings may be keys — validate where they are used (callsites).
- Look inside
assets/andres/values/strings.xmlfor config files or API keys.
Always trace each suspicious string back to where it is used in code (classes.dex / decompiled code) — context matters.
3.5 Decompile DEX to readable Java/Kotlin (jadx + navigation tips)
Open the APK in jadx-gui (jadx-gui app.apk) for interactive inspection. If you prefer CLI:
jadx -d jadx_out app.apk
When reading decompiled code:
- Search for entry points: Activities that start at login, classes that call network clients (
OkHttpClient,HttpUrlConnection,Retrofit) and crypto APIs. - Locate authentication flows: look for methods that build requests for
/auth, create JWTs, or handle refresh tokens. - Find pinning and TLS logic: search for
CertificatePinner,X509TrustManager,HostnameVerifier,TrustManager,SSLSocketFactory. - Find attestation logic: search for
PlayIntegrityManager,SafetyNet,attest,nonce,KeyAttestationorAndroidKeyStore. - Find root checks: search for common root-detection artifacts (calls that exec
su, checks forsubinaries, mount points, presence ofmagisk/magiskhide, reading/procvalues). Also search for library-based checks references (strings containingrootbeer).
When you find a candidate method, inspect its callers and call graph — who uses the result of that check? Does only the client read it, or is something sent to server?
3.6 Identify certificate pinning & attestation implementation (what to look for)
Pinning indicators:
CertificatePinnerorcertificatePinner(OkHttp).- Custom
X509TrustManagerimplementations orHostnameVerifieroverrides. - Code that loads pinned certs from
raw/resources orassets/(e.g.,CertificateFactory.getInstance("X.509")+ readingR.rawresource). - Calls to
TrustManagerFactorywith custom keystore from app resources.
Attestation indicators:
- Calls to
PlayIntegrityManagerAPI,SafetyNetclient, or Key Attestation code paths. - Generation of an attestation nonce and subsequent network call that sends a token to a backend endpoint.
- Use of
KeyStore.getInstance("AndroidKeyStore")and creation of keys withsetIsStrongBoxBacked()orKeyGenParameterSpec.Builder— indicates hardware-backed attempts.
Important: presence of client-side attestation calls is only an indicator—strong security requires the token to be validated on the backend. Flag the code and suggest verifying server-side token validation (nonce, signature verification, certificate chain, timestamps).
3.7 Find local integrity checks & tamper-detection
Search code for common integrity check patterns:
- Calls to
getPackageManager().getPackageInfo(...).signaturesorPackageManagersignature checks. - Calls to compute hashes of
classes.dex,resources.arsc, or loaded native libraries viaMessageDigest(SHA-256/SHA1). - Checks reading
/proc/self/exe,Runtime.getRuntime().exec("ls /system/xbin/su"),new File("/system/app/Superuser.apk").exists(), etc. - Checks against
Build.TAGS(e.g.,test-keys,dev-keys) orro.build.tagssystem property.
If checks exist, inspect whether they are used locally, or whether the app reports results to server. Client-only checks are easy to bypass; flag them and recommend server-side validation.
3.8 Detect obfuscation & string-protection patterns
Obfuscation cues:
- Many short class/method names like
a.a.bora$1, or classes namedR$1,aa,ab→ likely ProGuard/R8 obfuscation. - Lack of meaningful string literals (strings are randomized or encrypted).
- Presence of string decryption routines (a small method that maps bytes through XOR/shift + uses it everywhere to restore strings).
To identify string encryption:
- Search for repeated small methods that take an
int[]orbyte[]and returnString— these often are decryptor stubs. - Search for large base64 or hex blobs in
res/raworassets.
Example grep to find possible decryptor methods:
grep -R --line-number -iE "decode|decrypt|xor|obfuscate|obf|deobf" app_unpacked/ | head
If string-encryption is present, note the decryptor location and the calling sites — those call-sites are high-value for further analysis.
3.9 Native library analysis (.so) — quick triage and next steps
List .so files:
unzip -l app.apk | grep '\.so' > native_list.txt
Extract a sample and inspect:
unzip -p app.apk lib/arm64-v8a/libexample.so > libexample.so
file libexample.so
readelf -h libexample.so
readelf -s libexample.so | head -n 40
nm -D libexample.so | head
strings libexample.so | egrep -i "JNI_OnLoad|Java_|AES|Cipher|RSA|attest|key|keystore|play" | sort -u
What to record:
- Which ABIs are present (arm64-v8a, armeabi-v7a, x86).
- Whether symbols are present or the binary looks stripped (absence of
Java_com_or clear function names suggests stripped). - Strings in
.sothat hint at functionality (JNI_OnLoad,Java_com_company_module_method, crypto-related identifiers). - Exports that look like
Java_*— these indicate JNI bridges and help map native → Java callsites.
If binary looks non-trivial and contains security logic, plan to open it in Ghidra/Cutter for deeper reverse engineering (annotate function boundaries, identify cryptographic routines, search for attestation checks implemented in native code).
3.10 Static detection of anti-frida / anti-debug / anti-emulator
Search for code that detects instrumentation or debugger:
- Methods checking loaded processes for
frida-server,gdbserver,gdb, or checking ptrace status. - Use of
android.os.Debug.isDebuggerConnected()orDebug.waitForDebugger()calls in suspicious contexts. - Existence of native anti-debugging logic (
ptrace(PTRACE_TRACEME, ...)patterns,prctl(PR_SET_DUMPABLE, 0),anti_debugstrings). - Checks for emulator indicators (files
/system/bin/qemu_props, Android ID patterns,ro.kernel.qemu).
Example grep:
grep -R --line-number -iE "frida|gdb|ptrace|isDebuggerConnected|ro.kernel.qemu|qemu" app_unpacked/ | head
If anti-instrumentation code exists, document where it is and its control flow; this helps design safe lab workarounds (in lab only) and suggests hardening points.
3.11 Evaluate obfuscation vs real protection (risk estimation)
Important distinction:
- Obfuscation (ProGuard/R8) is mainly an obstacle to reverse engineering; it raises required effort but is not a cryptographic defence.
- String encryption + native attestation + hardware-backed keystore raise the bar significantly.
- Kernel-level protections / verified boot are platform level and out of scope for pure static analysis to fully validate — but you can detect whether the app attempts to rely on them (calls to hardware-backed APIs).
Estimate effort:
- Light obfuscation = low effort to recover with
jadx+ careful renaming. - String encryption + custom crypto in native = significant effort; require Ghidra & native expertise.
- Native code with stripped symbols + anti-debugging = high effort.
Record an approximate Reverse Effort label (Low / Medium / High) for each security-relevant artifact.
3.12 Produce the static-analysis findings (structure & examples)
For each finding produce a structured entry:
- ID: S-001
- Title: Hardcoded API key in
assets/config.json - Location:
app_unpacked/assets/config.json: line 12+ sha256 of file - Description: The API key
AK_abc...is stored in plain text and used byAuthClient.login() - Evidence: snippet of file & callsite in
jadx_out/com/example/auth/AuthClient.java:123 - Impact: Credential leakage → impersonation and API abuse.
- Likelihood: High (found in multiple callsites & included in build)
- Recommended remediation: Move to server-side, avoid embedding keys, use OAuth client credentials + ephemeral tokens.
- Next action: Dynamic validation in lab; check whether this key is accepted by backend (requires vendor coordination).
Create a JSON/Markdown file with all findings to include in reports and to use as a checklist for Module 4/5 follow-ups.
3.13 Example commands & small scripts (useful snippets)
A few repeatable commands to build your static findings quickly.
- Extract and decompile (one-liner pipeline):
APK="app.apk"
WORKDIR="work_$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$WORKDIR" && cp "$APK" "$WORKDIR/"
cd "$WORKDIR"
apktool d "$APK" -o app_unpacked
jadx -d jadx_out "$APK"
sha256sum "$APK" > apk.sha256
- Search for attestation/pinning/root indicators:
grep -R --line-number -iE "PlayIntegrity|SafetyNet|attest|KeyAttestation|AndroidKeyStore|CertificatePinner|HostnameVerifier|X509TrustManager|TrustManager|su|magisk|root|frida|gdb|ptrace" app_unpacked/ jadx_out/ | tee static_suspicious_hits.txt
- Produce a native libs summary:
unzip -l "$APK" | grep '\.so' | awk '{print $4}' > native_paths.txt
while read -r p; do
unzip -p "$APK" "$p" > "$(basename "$p")"
file "$(basename "$p")"
echo "Symbols for $(basename "$p")" >> native_summary.txt
readelf -s "$(basename "$p")" 2>/dev/null | head -n 50 >> native_summary.txt
echo -e "\n\n" >> native_summary.txt
done < native_paths.txt
3.14 Lab exercises (safe, authorized, with expected outcomes)
Lab A — Manifest & exported components audit
- Objective: produce a list of exported components and simulate the risk model.
- Steps: use
apktoolto decode manifest, list exported components, identify any withintent-filterand missingpermissionattributes. - Expected outcome: a table with component name, exported flag, permission, and suggested impact.
Lab B — Trace an auth flow in decompiled code
- Objective: from login Activity -> network client -> server endpoint, map where tokens are created, stored, and sent.
- Steps: open code in
jadx, find login activity, follow method calls to network client, identify token storage (SharedPreferences / Keystore). - Expected outcome: call graph diagram (simple list), and identification of insecure storage or poor token management.
Lab C — Native library triage
- Objective: find whether security logic is implemented in native code and whether it’s stripped.
- Steps: list
.sofiles, runstringsandreadelf, open in Ghidra if containing interesting strings. - Expected outcome: list of native functions of interest, classification: (contains crypto? yes/no), (stripped? yes/no), (recommended deeper analysis).
3.15 Common pitfalls & how to avoid them
- False positives from grep: always open the found string in code and verify it’s actually used in a security-sensitive flow.
- Assuming presence = protection: finding calls to attestation APIs doesn’t mean tokens are validated by the server — always require server-side evidence.
- Over-relying on decompiler output: decompilers are imperfect; confirm suspicious code by reading bytecode or smali where needed.
- Ignoring native code: some apps move critical checks into
.so— treat them as first-class citizens during static analysis.
3.16 Deliverables for Module 3
Produce the following artifacts:
static_manifest.json— APK metadata and checksums.manifest_findings.md— exported components & permission analysis (with line refs).strings_endpoints.csv— list of endpoints, hostnames, potential keys (with file + line + snippet).native_summary.txt— native libs inventory and quick readelf / strings snapshot.code_map.pdfor markdown — short call graph for auth, attestation, and pinning flows (where they live).static_findings.json— structured findings with impact & recommended next actions (dynamic verification steps or evidence to request from vendor).
3.17 Recommended follow-ups (after static)
- Use the static findings to design focused dynamic tests: attempt to exercise specific flows, capture attestation tokens, and collect logs (lab-only).
- If obfuscation or string encryption exists, plan native analysis (Ghidra) and allocate more time / expertise.
- If you identify attestation/pinning/root-checks, prepare the forensic artifact checklist (Module 13) so you can validate any vendor claims of bypass with raw tokens and device images.
3.18 Quiz / self-check (short)
- How do you distinguish a genuine, used API key from a placeholder found in
strings.xml? - What does a missing
Java_com_...export in a.sosuggest about the binary? - Why is it insufficient to only find a call to
PlayIntegrityManagerin the client to conclude the app is attested?
(Answers: 1) trace callsites where it is used and confirm backend acceptance; 2) likely stripped symbols — harder reverse; 3) need server-side token validation evidence: nonce, signature verification, timestamp.)
