Skip to content

Reversing

Static analysis and decompilation of Android applications.

The goal of static analysis is to read the app's code and resources without running it. Different tools give you different views of the same binary — understanding what each one does, when to reach for it, and how to chain them together is the core skill.


Tool Overview — When to Use What

Tool Input Output Best for
apktool APK Smali + resources Patching code, reading resources/manifest
jadx APK / DEX / AAR Java (pseudo-source) Reading logic, searching strings, tracing call graphs
dex2jar APK / DEX .jar (Java bytecode) Getting a JAR to open in JD-GUI or IntelliJ
JD-GUI .jar Java source view Quick visual read of a JAR; export all sources
MobSF APK Full automated report First-pass: permissions, hardcoded secrets, endpoints
APKHunt APK OWASP-mapped findings MASVS/OWASP compliance checks, CI integration
apkleaks APK Secrets + URLs Fast regex scan for keys, tokens, endpoints
androguard APK / DEX Call graph, CFG, fingerprint Call graph / CFG analysis, APK fingerprinting, diffs

apktool — Decode, Patch, Rebuild

What it does: Decodes an APK into Smali (human-readable Dalvik bytecode) and raw XML resources. You can edit the Smali or resources and rebuild a working APK. This is the primary tool when you need to patch the app — remove a root check, disable certificate pinning, flip a flag.

What it does not do: Produce readable Java. Smali is low-level assembly — readable with practice but not the same as reading decompiled Java.

# Decode — produces smali/, res/, AndroidManifest.xml, assets/ in the output dir
apktool d app.apk -o decoded/

# Decode without resource decoding (faster, use when you only care about smali/manifest)
apktool d app.apk -o decoded/ -r

# Rebuild after editing
apktool b decoded/ -o patched_unsigned.apk

# Align + sign (required before reinstalling)
zipalign -v 4 patched_unsigned.apk patched_aligned.apk
apksigner sign --ks my.keystore --ks-key-alias mykey patched_aligned.apk

# Install
adb install -r patched_aligned.apk

Typical workflow — disable a root check:

  1. apktool d app.apk -o decoded/
  2. grep -r "isRooted\|checkRoot\|RootBeer" decoded/smali/ — find the check
  3. Open the smali file — find the branch (if-eqz, if-nez) that leads to the "rooted — abort" path
  4. Change the branch condition to its inverse, or replace the method body with return-void
  5. apktool b decoded/ -o patched_unsigned.apk → align → sign → install

Common errors:

Error Fix
brut.AndrolibException: Could not decode Update apktool: apktool --version, download latest jar
Resources fail to decode Use -r flag to skip resource decoding
Rebuild fails on unknown opcode The app uses newer DEX features — use --use-aapt2 flag

jadx — DEX to Java (Primary Analysis Tool)

What it does: Decompiles DEX bytecode back into readable Java (pseudo-source). This is the first tool to open on any APK — it gives you class structure, method bodies, string literals, and full text search across the entire codebase in a GUI.

When to use: Reading logic, tracing what a method does, finding hardcoded values, understanding the call graph, locating where a permission is checked.

# GUI — opens the APK and shows a class tree + decompiled source
jadx-gui app.apk

# CLI — dumps all decompiled Java into output/ directory
jadx -d output/ app.apk

# CLI with extra options
jadx -d output/ \
    --show-bad-code \          # show methods jadx couldn't fully decompile
    --export-gradle \          # generate a build.gradle so you can open it in Android Studio
    app.apk

Key jadx GUI shortcuts:

Action Shortcut
Full text search across all decompiled code Ctrl+Shift+F
Find all usages of a class / method / field Right-click → Find Usage
Jump to declaration Ctrl+Click or F4
Navigate back Alt+Left
Search class by name Ctrl+N

What jadx produces vs reality:

jadx produces pseudo-Java — it is a best-effort reconstruction, not real source. Some constructs decompile poorly:

  • Heavy obfuscation → single-letter class/method names everywhere
  • Reflection → you see Class.forName("...") but the actual class resolved at runtime is opaque
  • Native methods → native keyword, no body, need to look in lib/*.so
  • ProGuard / R8 → control flow mangling, inlined methods, synthetic accessors

When jadx shows /* JADX WARNING: ... */ or marks a method with // decompile error, the smali for that method (from apktool) is more reliable.


dex2jar — DEX to JAR

What it does: Converts a DEX file (or APK) into a standard Java .jar file containing .class files. The JAR is not runnable on Android but any Java bytecode viewer — JD-GUI, CFR, IntelliJ's built-in decompiler, Procyon — can open it.

When to use: When jadx fails or produces unreadable output on a specific class. Different decompilers have different strengths — dex2jar + a second decompiler often recovers code that jadx garbles. Also useful when you want to open the app in IntelliJ IDEA as a regular Java project.

# Convert APK → JAR (extracts all DEX files inside the APK first)
d2j-dex2jar app.apk -o app.jar

# If the APK has multiple DEX files (multidex), they are merged into one JAR
# Check what DEX files exist first:
unzip -l app.apk | grep "\.dex"

# Convert a single DEX directly
d2j-dex2jar classes2.dex -o classes2.jar

# Handle errors — dex2jar sometimes crashes on heavily obfuscated DEX
# --force skips bad classes and continues
d2j-dex2jar --force app.apk -o app.jar

# Verify the JAR is valid
jar tf app.jar | head -20

After converting — open in another tool:

# Open in JD-GUI (see below)
jd-gui app.jar

# Or use CFR decompiler (often handles obfuscation better than JD-GUI)
java -jar cfr.jar app.jar --outputdir cfr_output/

# Or Procyon
java -jar procyon.jar -jar app.jar -o procyon_output/

dex2jar vs jadx — which wins:

Scenario Prefer
General analysis, search, navigation jadx (all-in-one)
jadx fails on a specific class dex2jar → CFR or Procyon
Need a JAR for IntelliJ / other tooling dex2jar
Want to script decompilation jadx CLI or androguard

JD-GUI — Java Decompiler GUI

What it does: Opens a .jar file and shows decompiled Java source in a point-and-click GUI. Simpler than jadx — no search across multiple files, no navigation graph — but fast to spin up and sometimes produces cleaner output than jadx on certain code patterns.

When to use: Quick inspection of a specific JAR, exporting all sources to disk for grep-based analysis, or as a fallback when jadx output is unreadable.

Process:
  APK → dex2jar → app.jar → JD-GUI
# Launch (assumes jd-gui is on PATH, or run the .jar directly)
jd-gui app.jar

# Or run the jar
java -jar jd-gui-*.jar app.jar

Inside JD-GUI:

  • Left panel — package/class tree
  • Right panel — decompiled Java source for the selected class
  • File → Save All Sources — exports every class as a .java file into a ZIP — useful for then grepping across everything:
# Export from JD-GUI: File → Save All Sources → sources.zip
unzip sources.zip -d sources/
grep -r "password\|api_key\|secret" sources/
grep -rE "https?://" sources/

Limitation: JD-GUI has no cross-reference navigation, no search across files in the GUI, and frequently fails on obfuscated code. It is best used as a "dump sources then grep" pipeline, not as an interactive analysis environment.


MobSF — Automated Static Analysis

What it does: Uploads an APK and runs a full automated analysis: manifest permissions, exported components, hardcoded secrets, dangerous API calls, URL endpoints, certificate info, and an overall risk rating. Output is a browsable HTML report.

When to use: First-pass analysis at the start of every engagement. MobSF surfaces the obvious issues in minutes — then you do manual deep-dive on the findings it flags.

Setup — Python virtual environment:

# Prerequisites: Python 3.10+, JDK 17+, Git
git clone https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
cd Mobile-Security-Framework-MobSF

# Create and activate a venv
python -m venv venv
source venv/bin/activate          # Linux / macOS
# venv\Scripts\activate           # Windows

# Install dependencies
pip install -r requirements.txt

# First-time setup (creates the database, downloads rules)
python manage.py migrate

# Start the server
python manage.py runserver 127.0.0.1:8000

Open http://127.0.0.1:8000 in a browser, drag-and-drop the APK, and the analysis runs automatically.

Subsequent runs — just activate the venv and start the server:

cd Mobile-Security-Framework-MobSF
source venv/bin/activate
python manage.py runserver 127.0.0.1:8000

API — automate uploads without the browser:

# Get your API key from http://127.0.0.1:8000 → REST API docs
export MOBSF_KEY="your-api-key-here"

# Upload
curl -F "file=@app.apk" \
    http://127.0.0.1:8000/api/v1/upload \
    -H "Authorization: $MOBSF_KEY"
# Returns: {"analyzer":"static_analyzer","status":"success","hash":"<hash>", ...}

# Trigger analysis
curl -X POST \
    --url http://127.0.0.1:8000/api/v1/scan \
    --data "scan_type=apk&file_name=app.apk&hash=<hash>" \
    -H "Authorization: $MOBSF_KEY"

# Get JSON report
curl -X POST \
    --url http://127.0.0.1:8000/api/v1/report_json \
    --data "hash=<hash>" \
    -H "Authorization: $MOBSF_KEY" \
    -o report.json

# Get PDF report
curl -X POST \
    --url http://127.0.0.1:8000/api/v1/download_pdf \
    --data "hash=<hash>" \
    -H "Authorization: $MOBSF_KEY" \
    -o report.pdf

What to look at in the MobSF report:

Section What to check
Manifest Analysis Exported components, dangerous permissions, allowBackup, debuggable
Binary Analysis Hardcoded strings, API keys, URLs
Code Analysis Dangerous API calls: Runtime.exec, reflection, crypto misuse
Network Security Cleartext traffic allowed, pinning disabled, custom trust managers
File Analysis Files inside the APK — look for embedded DBs, config files, certs
Signer Certificate Who signed it, which scheme (v1/v2/v3), self-signed vs store cert

MobSF as CI gate: You can POST an APK to the API and get a JSON score back — integrate as a pipeline step to catch obvious issues on every build.


APKHunt — OWASP MASVS Static Scanner

What it does: Scans a decoded APK against the OWASP Mobile Application Security Verification Standard (MASVS) and outputs findings mapped to specific MASVS controls. Produces a text report you can reference directly in pentest reports.

When to use: When you need OWASP-aligned findings for a report, or want a second automated opinion after MobSF. APKHunt focuses on code-level patterns — it reads smali and resources — while MobSF focuses more on manifest and network config.

# Install
pip install apkhunt
# or clone and run directly
git clone https://github.com/Cyber-Buddy/APKHunt
cd APKHunt

# Run against an APK
python3 apkhunt.py -p app.apk

# Output goes to a .txt report in the current directory
# Each finding references the MASVS control it violates (e.g. MSTG-STORAGE-2)

What APKHunt checks:

Category Examples
Storage Hardcoded credentials, world-readable files, SharedPreferences with sensitive data
Crypto Hardcoded keys/IVs, ECB mode, weak algorithms
Network Cleartext HTTP, disabled hostname verification, custom trust managers
Platform Exported components with no permission, JavaScript enabled in WebViews, intent injection
Code quality Logging of sensitive data, stack traces exposed, debug flags
# View the report
cat apkhunt_report_*.txt | less

apkleaks — Fast Secrets and Endpoint Extraction

What it does: Runs a set of regex patterns against strings extracted from the APK to find hardcoded secrets, API keys, tokens, and URLs. Much faster than manual grep — finishes in seconds.

When to use: At the very start of an engagement alongside MobSF. Run it immediately on every APK to surface any obvious credential leaks before diving into code.

# Install
pip install apkleaks

# Run
apkleaks -f app.apk

# Output to JSON
apkleaks -f app.apk -o findings.json

# Use a custom pattern file (add your own regexes)
apkleaks -f app.apk -p custom_patterns.json

Sample output:

[!] Google API Key
    AIzaSyD...

[!] Firebase URL
    https://myapp-default-rtdb.firebaseio.com

[!] AWS Access Key
    AKIAIOSFODNN7EXAMPLE

[!] URL
    https://api.internal.example.com/v2/admin

Each finding includes the pattern name and the matched value. False positives happen — validate any finding before reporting.


androguard — CLI Analysis Tool

What it does: Parses APKs and DEX files to produce call graphs, control flow graphs, APK fingerprints, and decompiled output. Install it and run directly from the terminal.

When to use: Call graph and CFG analysis, APK fingerprinting, quick diffs between two APK versions — things that would be tedious to trace manually in jadx.

pip install androguard
androguard --help

Fingerprint an APK

# Package name, version, SHA256, signing cert info, permissions used
androguard apkid app.apk

Sample output:

file_type    : APK
magic        : Zip archive data
app_name     : MyApp
package      : com.example.app
version_name : 1.4.2
version_code : 42
main_act     : com.example.app.MainActivity
min_sdk      : 21
target_sdk   : 33
cert_sha1    : AB:CD:EF:...
cert_issuer  : CN=Android Debug, O=Android, C=US
# Broader analysis summary — class/method/string counts, permissions, activities
androguard analyze app.apk

Call Graph

The call graph maps every method-to-method call in the app. It lets you see which code paths are reachable from an entry point (an exported Activity, Service, etc.) and whether they reach sensitive methods.

# Export call graph to DOT format (render with graphviz)
androguard cg app.apk -o callgraph.dot

# Export to GML format (open in Gephi or Cytoscape for interactive exploration)
androguard cg app.apk -o callgraph.gml

Render the DOT file:

# PNG — good for small apps
dot -Tpng callgraph.dot -o callgraph.png

# SVG — scalable, better for large graphs
dot -Tsvg callgraph.dot -o callgraph.svg

# Open the SVG in a browser and use Ctrl+F to search for class/method names

Reading the call graph:

Each node is a method in Dalvik descriptor format:

Lcom/example/app/MainActivity;->onCreate(Landroid/os/Bundle;)V
│                               │         │                   │
class (L...;)              method name   argument types   return type

Edges point from caller → callee. To trace an attack path:

  1. Open the rendered graph or load the GML into Gephi
  2. Find the exported entry point (e.g. MainActivity->onCreate)
  3. Follow edges forward — does any path reach rawQuery, exec, decrypt, or other sensitive methods?

Gephi workflow for large graphs:

1. Open Gephi → File → Open → callgraph.gml
2. Run Layout → ForceAtlas2 to space out nodes
3. Use Filters → Topology → Ego Network on your entry point node
   → shows only methods reachable from that node
4. Ctrl+F to search for a method name

Decompile + Control Flow Graphs (androguard decompile)

CFGs are not a separate command — they are generated by androguard decompile using the -f flag. The -o output directory is required. Running it produces:

  • .java files — decompiled pseudo-Java for each class
  • .ag files — smali-like instruction dump per method
  • CFG image or DOT file per method (when -f is specified)
# Basic decompile — output Java to a directory (-o is required)
androguard decompile -o output/ app.apk

# Decompile + generate PNG CFG for every method
androguard decompile -o output/ -f png app.apk

# Generate SVG CFGs (better for zooming large methods)
androguard decompile -o output/ -f svg app.apk

# Generate raw DOT files (render manually with graphviz)
androguard decompile -o output/ -f raw app.apk

# Limit to a specific package namespace (regex) — much faster on large apps
androguard decompile -o output/ -f png \
    --limit "^Lcom/example/app/.*" app.apk

# Limit to a single class
androguard decompile -o output/ -f png \
    --limit "^Lcom/example/app/LicenseCheck;.*" app.apk

# Use DEX2JAR to also produce a JAR file alongside
androguard decompile -o output/ -j app.apk

# Use a specific decompiler (default is DAD)
androguard decompile -o output/ -d dad app.apk

Prerequisites for CFG image output:

sudo apt-get install graphviz
pip install -U pydot

What the output directory contains:

output/
  com/example/app/
    LicenseCheck.java      ← decompiled pseudo-Java
    LicenseCheck.ag        ← smali-like per-method instruction dump
    LicenseCheck_validate.png   ← CFG image for the validate() method (if -f png)
    ...

Reading the .ag file:

The .ag file is a smali-like text dump of each method's basic blocks — useful when the Java decompilation is garbled:

# Lcom/example/app/LicenseCheck;->validate(Ljava/lang/String;)Z

validate-BB@0x0 : [ validate-BB@0x12 validate-BB@0x1e ]
    0    (00000000) invoke-virtual   v0, Ljava/lang/String;->isEmpty()Z
    1    (00000006) move-result      v1
    2    (00000008) if-eqz           v1, +7          ← branch: if v1==0 go to BB@0x12

validate-BB@0x12 : [ validate-BB@0x1e ]
    3    (00000012) const/4          v0, 0x0
    4    (00000014) return           v0              ← returns false (invalid)

validate-BB@0x1e :
    5    (0000001e) ...              (rest of real validation)

The [ ... ] after each block header lists the successor blocks — that is the branch table in text form.


Reading the CFG image:

Each box is a basic block. Arrows are:

  • Solid → unconditional jump or fall-through
  • Dashed / labelled true/false → conditional branch (if-eqz, if-nez, if-gt, etc.)

To find the bypass point:

  1. Find the block that calls the check method (invoke-virtual isRooted)
  2. Follow the move-result → find the if-eqz/if-nez branch
  3. One branch leads to normal execution, the other to an exception or return false
  4. In the smali file, flip the branch condition or replace the block with a constant return
┌──────────────────────────────┐
│ invoke-virtual  isRooted()Z  │
│ move-result     v0           │
│ if-eqz          v0, +offset  │ ← v0==0 means not rooted → normal
└──────┬─────────────┬─────────┘
       │ (false/0)   │ (true/not 0)
  ┌────▼──────┐  ┌───▼──────────┐
  │ continue  │  │ throw Sec.Ex │ ← patch: change if-eqz → if-nez to invert
  └───────────┘  └──────────────┘

Obfuscation — Identifying and Working Around It

Modern Android apps are usually processed by ProGuard or R8 before release. The goal is not real secrecy; it is to slow static analysis by renaming classes, methods, and fields and sometimes flattening control flow.

How to Recognise It

Common signs in jadx or JD-GUI:

  • Class names like a, a0, b1, c$a
  • Packages that look like x.y.z instead of meaningful namespaces
  • Methods named a(), b(), c() everywhere
  • Real third-party package names still intact: okhttp3, retrofit2, androidx, kotlin
  • Meaningful strings and manifest component names still present

What usually survives obfuscation:

  • AndroidManifest.xml component names
  • URL paths, domains, JSON keys, SQL table names
  • Third-party library APIs
  • Resource names and string constants
  • Native library names loaded by System.loadLibrary

Practical Strategy

When names are useless, pivot from names to behaviour:

  1. Start at the manifest — exported components, deep links, services, receivers
  2. Search for stable strings: login, premium, token, auth, purchase, root, debug
  3. Follow network clients (okhttp3, retrofit2) and parsers (Gson, Moshi, JSONObject)
  4. Cross-check suspicious methods in smali when jadx output becomes unclear

Useful searches:

# Search for meaningful strings instead of class names
grep -ri "login\|token\|premium\|root\|debug\|purchase" output/

# Search for common library anchors
grep -ri "okhttp3\|retrofit2\|RootBeer\|WebView" output/

If You Have a Mapping File

If the client accidentally ships or leaks a ProGuard / R8 mapping file (mapping.txt), use it immediately. It restores real class and method names.

# Google Retrace tool
retrace mapping.txt stacktrace.txt

In practice, pentests rarely get the mapping file. Assume you need to work without it.

When to Drop to Smali

Obfuscation often hurts the decompiler more than the bytecode. Switch from JADX to Smali when:

  • JADX shows /* JADX WARNING */ or decompile errors
  • Control flow looks impossible or heavily flattened
  • Multiple short methods appear to forward to one real check
  • You need the exact branch that decides true vs false

The names may still be ugly, but the bytecode is definitive.


Native Libraries and JNI

Not all interesting logic is in DEX. Apps frequently move checks and secrets into native libraries under lib/ to make reversing harder.

What to Look For in lib/

Common high-value targets:

  • Root / emulator / debugger detection
  • Crypto helpers and hardcoded keys
  • Certificate pinning or custom TLS wrappers
  • License checks and anti-tamper logic
  • Game logic or protobuf parsers

Static recon:

# List libraries inside the APK
unzip -l app.apk | grep "\.so$"

# Extract and inspect one library
unzip app.apk "lib/arm64-v8a/libfoo.so" -d extracted/

# Quick strings pass
strings extracted/lib/arm64-v8a/libfoo.so | less

# Search for suspicious strings
strings extracted/lib/arm64-v8a/libfoo.so | grep -Ei "token|secret|apikey|root|magisk|frida|debug|https://|http://"

JNI Recon

Search the decompiled Java for the bridge layer:

grep -ri " native " output/
grep -ri "System.loadLibrary\|System.load" output/

If you find:

public native boolean isRooted();
static {
    System.loadLibrary("security");
}

then libsecurity.so is the next file to inspect in Ghidra, IDA, Binary Ninja, or radare2 / rizin.

Useful ELF Triage Commands

# Architecture and file type
file libfoo.so

# Imported and exported symbols
readelf -Ws libfoo.so | less

# Dynamic dependencies
readelf -d libfoo.so

# Section headers
readelf -S libfoo.so

# Exported symbol names only
nm -D libfoo.so

If the binary is stripped, symbol names may be sparse. Fall back to strings, cross-references to JNI_OnLoad, and JNI registration tables.

Secrets Hidden in Native Code

Developers often move keys from Java into native code assuming that is "secure enough". It is not. Search for:

  • API keys and bearer tokens in .rodata
  • Certificate pins and hashes
  • AES keys, IVs, salts
  • Hostnames for staging, admin, or internal APIs

Even when the key is built at runtime, the source fragments often still exist as strings or constant byte arrays.

Ghidra Workflow for Android .so Files

  1. Import the .so into Ghidra
  2. Confirm the correct processor (AArch64, ARM, x86_64)
  3. Find JNI_OnLoad and cross-reference outward
  4. Search for exported Java_* symbols or RegisterNatives
  5. Search the Strings window for root, frida, debug, premium, token, URLs
  6. Rename functions as you identify them to build your own map

This is usually enough to bridge static reversing into a precise Frida native hook.


Diffing Two APK Versions

When a vendor fixes a bug quietly, the fastest way to understand the change is to diff the old and new APKs. This is especially useful for:

  • Reproducing a patched vulnerability
  • Identifying exactly where auth logic changed
  • Finding newly added pinning, integrity, or root checks
  • Spotting updated API endpoints or feature flags

Fast Triage

# Fingerprint both APKs
androguard apkid old.apk
androguard apkid new.apk

# Compare manifest summaries
aapt dump badging old.apk > old.badging.txt
aapt dump badging new.apk > new.badging.txt
diff -u old.badging.txt new.badging.txt

Things to compare first:

  • versionCode / versionName
  • targetSdkVersion
  • Permissions added or removed
  • New exported components
  • New native libraries or removed ones

Decode Both and Diff the Trees

apktool d old.apk -o old_decoded/
apktool d new.apk -o new_decoded/

diff -ru old_decoded/ new_decoded/

This quickly surfaces:

  • Manifest changes
  • New XML config like network_security_config.xml
  • Smali changes in security-sensitive methods
  • New assets, certs, or feature flags

Diff Decompiled Java

jadx -d old_java/ old.apk
jadx -d new_java/ new.apk

diff -ru old_java/ new_java/

Use this when you want a semantic view rather than raw smali.

Prioritise High-Value Diffs

Start by searching only the code likely to matter:

diff -ru old_java/ new_java/ | grep -Ei "root|debug|pin|trust|token|auth|premium|license|integrity"

If the diff is huge, focus on:

  • AndroidManifest.xml
  • res/xml/network_security_config.xml
  • Classes containing login, auth, session, payment, premium
  • lib/*.so additions or changes

Diff Native Libraries

# Compare library inventories
find old_decoded/lib -type f | sort > old_libs.txt
find new_decoded/lib -type f | sort > new_libs.txt
diff -u old_libs.txt new_libs.txt

# Compare strings for a specific library
strings old_decoded/lib/arm64-v8a/libfoo.so > old_libfoo.txt
strings new_decoded/lib/arm64-v8a/libfoo.so > new_libfoo.txt
diff -u old_libfoo.txt new_libfoo.txt

If a new security library appears, assume the patch likely moved there.


What to Look For

Hardcoded Secrets

# In decoded smali / resources
grep -ri "api_key\|apikey\|secret\|password\|token\|bearer\|authorization" decoded/

# In decompiled Java sources (after JD-GUI export or jadx CLI)
grep -ri "api_key\|secret\|password\|token" output/

# Or let apkleaks do it automatically
apkleaks -f app.apk

Endpoints and URLs

grep -riE "https?://[a-zA-Z0-9./_-]+" decoded/smali/
grep -riE "https?://[a-zA-Z0-9./_-]+" output/

# Native libraries too
find extracted/ -name "*.so" -exec strings {} \; | grep -Ei "https?://[a-zA-Z0-9./_-]+"

Crypto Misuse

Look for in jadx:

  • AES/ECB — ECB mode, deterministic, insecure
  • Hardcoded byte arrays near SecretKeySpec — hardcoded AES key
  • Hardcoded byte arrays near IvParameterSpec — hardcoded IV
  • MD5 or SHA1 used as password hash — weak
  • SecureRandom seeded with a constant — predictable
grep -ri "AES/ECB\|MD5\|SHA1\|SecretKeySpec\|IvParameterSpec" output/

Logging Sensitive Data

grep -ri "Log\.d\|Log\.e\|Log\.i\|Log\.v\|Log\.w\|System\.out\.print" output/

Check what is being logged — auth tokens, passwords, and PII in logcat are common findings.

SQLite — Raw Query Injection Surface

grep -ri "rawQuery\|execSQL\|compileStatement" output/

Any string concatenation into these calls is SQL injection.

WebView JavaScript

grep -ri "setJavaScriptEnabled\|addJavascriptInterface\|loadUrl\|evaluateJavascript" output/

addJavascriptInterface with @JavascriptInterface on methods exposes Java to loaded web content — high risk if the WebView loads remote URLs.

Root / SSL Pin Detection

# Root checks
grep -ri "isRooted\|RootBeer\|checkRoot\|/system/xbin/su\|/system/bin/su" output/

# Certificate pinning
grep -ri "CertificatePinner\|X509TrustManager\|checkServerTrusted\|OkHttp\|TrustKit" output/

Native Libraries

# Strings and high-signal keywords in .so files
find extracted/ -name "*.so" -exec strings {} \; | grep -Ei "token|secret|apikey|root|magisk|frida|debug|premium|license"

# JNI exports
find extracted/ -name "*.so" -exec nm -D {} \; | grep "Java_"

If Java-side analysis looks clean but the app still behaves defensively at runtime, assume the interesting logic may be native.


For a new APK:

1. apkleaks -f app.apk              — secrets and URLs in 10 seconds
2. MobSF upload                     — automated first-pass report
3. jadx-gui app.apk                 — manual read of manifest, key classes
4. APKHunt -p app.apk               — OWASP-mapped findings for report
5. apktool d app.apk -o decoded/    — when you need to patch or read smali
6. dex2jar → CFR/JD-GUI             — fallback for classes jadx fails on
7. Review obfuscation + native libs  — identify `.so` targets and ugly code paths
8. androguard cg / decompile -f     — call graph + per-method CFG images
9. Diff old vs new APKs             — identify silent fixes and moved logic