Back to blog
Security 10 min

Static APK Analysis with JADX and Ghidra

May 26, 2026

Why static analysis

Before instrumenting an app with Frida, static analysis lets you map:

  • Security implementations (SSL Pinning, root detection, encryption)
  • Hardcoded secrets (API keys, tokens, internal endpoints)
  • Authentication and authorization logic
  • Native libraries requiring analysis with Ghidra

The JADX (Java/Kotlin code) + Ghidra (native C/C++ code) combination covers practically 100% of an APK's attack surface.


JADX — Decompiling Java and Kotlin code

Installation

# Via package manager (Linux)
sudo apt install jadx

# Or download the release at: https://github.com/skylot/jadx/releases
# jadx-gui (graphical interface) or jadx (CLI)

Opening the APK

# CLI: exports to directory
jadx app.apk -d jadx_output/

# GUI (recommended for exploration)
jadx-gui app.apk

Output structure

jadx_output/
├── sources/
│   └── com/example/app/
│       ├── MainActivity.java       # Activities
│       ├── network/ApiClient.java  # HTTP client
│       └── security/PinManager.java
└── resources/
    ├── AndroidManifest.xml
    └── res/values/strings.xml      # App strings

What to look for in decompiled code

1. Hardcoded secrets

# Search for tokens, keys and passwords in the code
grep -r "api_key\|apiKey\|secret\|password\|token\|Bearer" jadx_output/sources/ -i

# Search in strings.xml and other resources
grep -r "key\|secret\|endpoint" jadx_output/resources/ -i

Common finding example:

// Found in ApiClient.java
private static final String API_KEY = "sk-live-xxxxxxxxxxx";
private static final String BASE_URL = "https://internal-api.company.com";

2. SSL Pinning implementation

grep -r "CertificatePinner\|checkServerTrusted\|TrustManager\|X509" \
     jadx_output/sources/ -l

Example to identify which hosts have active pinning:

// PinManager.java
new CertificatePinner.Builder()
    .add("api.company.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build();

3. Root and Frida detection

grep -r "su\|superuser\|RootBeer\|frida\|/proc/maps\|magisk" \
     jadx_output/sources/ -i -l

4. Cryptography

grep -r "AES\|DES\|RSA\|Cipher\|SecretKey\|KeyStore" \
     jadx_output/sources/ -i

Watch for hardcoded keys:

// Vulnerable: hardcoded AES key
byte[] key = "1234567890abcdef".getBytes();
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");

AndroidManifest.xml — Permissions and exposed components

cat jadx_output/resources/AndroidManifest.xml

What to check:

  • android:exported="true" on Activities, Services and BroadcastReceivers — can be called by other apps
  • android:debuggable="true" — enables debugging in production (critical vulnerability)
  • Excessive permissions (READ_CONTACTS, CAMERA, etc. without justification)
<!-- Exported component without permission — vulnerable to intent injection -->
<activity android:name=".DeepLinkActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <data android:scheme="app" android:host="open"/>
    </intent-filter>
</activity>

Ghidra — Analyzing native libraries (.so)

Apps with native code compile to .so (ARM/ARM64 shared objects). JADX does not decompile C/C++ — this is where Ghidra comes in.

Installation

# Download at: https://ghidra-sre.org/
# Requires Java 17+
unzip ghidra_*.zip
./ghidra_*/ghidraRun

Extracting libs from APK

# APK is a ZIP — extract directly
unzip app.apk "lib/*" -d extracted_libs/

# Structure:
# extracted_libs/lib/
# ├── arm64-v8a/libnative.so
# └── armeabi-v7a/libnative.so

Ghidra analysis workflow

  1. New Project → Import File → select libnative.so
  2. Ghidra automatically detects ARM/ARM64
  3. Accept auto-analysis (~30s for large libs)
  4. In the Symbol Tree, go to Functions to see exported functions

Identifying JNI functions

Functions called by Java follow the pattern Java_package_Class_method:

Java_com_example_app_SecurityManager_validateToken
Java_com_example_app_CryptoUtils_decrypt

Reading decompiled code (Decompiler view)

Example of hardcoded AES key in native code:

// Decompiled by Ghidra
void Java_com_example_CryptoUtils_decrypt(JNIEnv *env, jobject obj) {
    char key[16] = {0x41, 0x42, 0x43, 0x44, ...}; // "ABCD..."
    AES_set_decrypt_key(key, 128, &aes_key);
    // ...
}

Searching for suspicious strings

In Ghidra: Search → For Strings — lists all strings present in the lib:

  • Endpoint URLs
  • Keys and tokens
  • Internal error messages

Complete static analysis workflow

1. Extract APK
   └── unzip app.apk / adb pull

2. JADX — Java/Kotlin code analysis
   ├── Hardcoded secrets and keys
   ├── SSL Pinning implementation (hosts and hashes)
   ├── Root/Frida detection (methods and libs used)
   └── Exported components in Manifest

3. Ghidra — Native library analysis (.so)
   ├── Exported JNI functions
   ├── Cryptographic keys in memory
   └── Validation logic in C/C++

4. Document findings for report
   └── Mapping: attack surface → bypass via Frida

Complementary tools

| Tool | Use | |------|-----| | apkanalyzer | Android SDK CLI for quick inspection | | MobSF | Automated static + dynamic analysis | | objection | Frida wrapper with ready-to-use pentest commands | | apksigner | Verifies APK signature and integrity |


References

Did you enjoy the content?

If you're building a system in this area, we can help. Talk to a specialist.

Schedule Consultation
Static APK Analysis with JADX and Ghidra — APCosta — APCosta