← Back to writing
Mobile / Android

APK Analysis Guide

May 07, 2024
4 min read
lawbyte

Static analysis of an APK can reveal API keys, backend endpoints, hardcoded credentials, cryptographic weaknesses, and insecure configurations — all before you launch a single dynamic test. This guide walks through the full static analysis workflow.

Obtaining the APK

From a connected device

# Find the package name
adb shell pm list packages | grep target

# Get the APK path
adb shell pm path com.target.app
# output: package:/data/app/com.target.app-1/base.apk

# Pull it
adb pull /data/app/com.target.app-1/base.apk target.apk

From Google Play (no device)

Tools: APKPure, APKCombo, gplaycli, or the apkeep utility:

apkeep -a com.target.app -d GooglePlay -e email@gmail.com -p password .

Split APKs (app bundle)

Modern apps use split APKs. Pull all of them:

adb shell pm path com.target.app
# May show multiple paths: base.apk, split_config.arm64_v8a.apk, etc.

# Pull all
adb pull /data/app/com.target.app~.../ ./apks/

Merge with APKEditor:

java -jar APKEditor.jar m -i apks/ -o merged.apk

Decompilation with jadx

# GUI — recommended for exploration
jadx-gui target.apk

# CLI — for scripting
jadx -d output/ target.apk

# Decompile with resource decoding
jadx -d output/ --show-bad-code target.apk

# Search across all decompiled sources
grep -r "api_key\|apikey\|secret\|password\|token" output/sources/ -i --include="*.java"

jadx search tips

In jadx-gui use Text Search (Ctrl+F) and Class Search (Ctrl+N). Useful search terms:

password
apiKey
secret
hardcoded
BuildConfig
okhttp
retrofit
AES
MD5
BASE64
SharedPreferences
MODE_WORLD_READABLE

AndroidManifest.xml Review

The manifest is the most important file to review first. jadx decodes it from resources.arsc.

# With apktool (more accurate decode)
apktool d target.apk -o output/
cat output/AndroidManifest.xml

Key things to check:

android:debuggable="true"

Debug flag left in production — allows adb shell run-as com.target.app to access the private data directory without root.

adb shell run-as com.target.app ls /data/data/com.target.app/

android:allowBackup="true"

ADB backup can extract the app’s data:

adb backup -noapk com.target.app
dd if=backup.ab bs=1 skip=24 | python -c "import zlib,sys;sys.stdout.buffer.write(zlib.decompress(sys.stdin.buffer.read()))" | tar -xvf -

Exported components

Any component with exported="true" (or no permission) is accessible from other apps or via ADB.

# Find exported activities, services, providers, receivers
grep -E 'exported="true"' output/AndroidManifest.xml

# Access exported activity
adb shell am start -n com.target.app/.AdminActivity

Content Providers

Exported providers can leak data to other apps:

# List providers
drozer console connect
run app.provider.info -a com.target.app
run app.provider.query content://com.target.app.provider/users

# SQL injection in provider
run app.provider.query content://com.target.app.provider/users --projection "* FROM users--"

Custom Permissions

Check if sensitive actions are protected only by android:protectionLevel="normal" (any app can request it):

<permission android:name="com.target.app.ADMIN"
android:protectionLevel="normal"/> <!-- BAD: should be signature -->

Secrets Hunting

In source code

# API keys and tokens
grep -rE "['\"][A-Za-z0-9_-]{20,}['\"]" output/sources/ | grep -iE "key|token|secret|auth"

# AWS credentials
grep -rE "AKIA[0-9A-Z]{16}" output/

# Hardcoded URLs
grep -rE "https?://[a-zA-Z0-9.-]+\.[a-z]{2,}" output/sources/ | grep -v "schema\|xmlns\|google\|android"

# Firebase URLs
grep -r "firebaseio.com\|googleapis.com" output/

# Private keys
grep -r "BEGIN PRIVATE KEY\|BEGIN RSA" output/

In resources and assets

# String resources
cat output/res/values/strings.xml | grep -iE "key|token|secret|api|url|pass"

# Raw assets
find output/assets/ -type f | xargs grep -l "api\|key\|token\|secret" 2>/dev/null

# Config files
find output/ -name "*.json" -o -name "*.xml" -o -name "*.properties" | xargs grep -l "secret\|key\|token"

In BuildConfig

// output/sources/com/target/app/BuildConfig.java
public static final String API_KEY = "sk-prod-xxxxxxxxxxxxxxxx";
public static final String BASE_URL = "https://internal-api.target.com";

Encrypted strings — check for static keys

// Look for AES/DES with hardcoded key
SecretKeySpec key = new SecretKeySpec("hardcodedkey12345".getBytes(), "AES");

Network Configuration Analysis

# Check network security config
cat output/res/xml/network_security_config.xml

If the config allows cleartext or trusts user/debug CAs:

<!-- Dangerous — all cleartext allowed -->
<base-config cleartextTrafficPermitted="true"/>

<!-- Only dangerous in debug builds -->
<debug-overrides>
<trust-anchors>
<certificates src="user"/>
</trust-anchors>
</debug-overrides>

SSL Pinning Detection

Look for pinning implementations in source:

grep -r "CertificatePinner\|TrustManager\|checkServerTrusted\|pin\|pinning" output/sources/ -l

Common pinning libraries:

// OkHttp CertificatePinner
CertificatePinner pinner = new CertificatePinner.Builder()
.add("api.target.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();

// TrustKit
TrustKit.initializeWithNetworkSecurityConfig(this);

// Manual X509TrustManager
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// custom verification
}

Smali Patching

Smali is the assembly language of Dalvik. Modify it to patch out security checks.

# Decompile to smali
apktool d target.apk -o output/

# Find the check (e.g., root detection)
grep -r "isRooted\|detectRoot" output/smali/ -l

# Edit the smali file — replace return-false with return-true
# Before:
const/4 v0, 0x0 # false
return v0

# After:
const/4 v0, 0x1 # true
return v0

# Recompile
apktool b output/ -o patched.apk

# Sign the APK
keytool -genkey -v -keystore test.keystore -alias test -keyalg RSA -keysize 2048 -validity 10000
apksigner sign --ks test.keystore --out signed.apk patched.apk

# Install
adb install signed.apk

Automated Analysis — MobSF

docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf

# Upload via API
curl -F "file=@target.apk" http://localhost:8000/api/v1/upload \
-H "Authorization: <api_key>"

MobSF checks for: exported components, dangerous permissions, hardcoded secrets, insecure crypto, cleartext traffic, outdated SDK versions, and more.


Analysis Checklist

  • debuggable=true in manifest
  • allowBackup=true in manifest
  • Exported components without permission requirements
  • Hardcoded API keys, tokens, passwords in source/resources
  • Firebase/AWS credentials in BuildConfig or assets
  • Cleartext HTTP traffic permitted
  • User/system CA trust configuration
  • SSL pinning implementation identified
  • Dangerous permissions (READ_CONTACTS, READ_SMS, CAMERA etc.)
  • Outdated SDK minSdkVersion (< 21)
  • Insecure cryptography (MD5, SHA1, ECB mode, hardcoded IV)

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.