Android RE Lab: From APK to Source – Decompiling & Analyzing Apps with Jadx & Ghidra
Android application reverse engineering (RE) is a critical skill for security researchers, penetration testers, and malware analysts. It involves transforming compiled applications back into a human-readable format to understand their functionality, identify vulnerabilities, or analyze malicious behavior. This expert-level guide will walk you through a comprehensive Android RE workflow, leveraging two powerful tools: Jadx for Java/Smali decompilation and Ghidra for native code analysis.
Prerequisites
Before we begin, ensure you have the following tools installed and basic familiarity with their interfaces:
- Jadx: A DEX to Java decompiler. Download from its GitHub releases page.
- Ghidra: NSA’s powerful software reverse engineering suite. Download from its official website.
- An Android APK file: For practical application. We recommend using a non-sensitive, open-source, or custom-built app for this lab.
- Basic understanding of Java, Android architecture, and assembly concepts.
Phase 1: Initial Decompilation and High-Level Analysis with Jadx
Jadx is an indispensable tool for quickly obtaining a readable Java representation from an Android application package (APK). It handles the conversion from Dalvik Executable (DEX) bytecode to Smali, and then to Java source code, making it easy to understand the app’s logic without diving into low-level assembly initially.
Step-by-Step Jadx Workflow:
- Open the APK: Launch Jadx GUI. Go to
File>Open file...and select your target APK. Jadx will automatically decompile the DEX files within the APK. - Navigate the Codebase: Once decompiled, Jadx presents a tree view of packages, classes, and resources. You can navigate through the Java source code directly.
- Search for Interesting Patterns: Use the search functionality (
Ctrl+Shift+ForEdit>Find Text) to locate keywords. Common search targets include:- API keys (e.g., “API_KEY”, “CLIENT_ID”)
- URLs/Endpoints (e.g., “http://”, “https://”)
- Sensitive method names (e.g., “encrypt”, “decrypt”, “login”, “authenticate”, “sendPassword”)
- Database operations (e.g., “SQLite”, “query”, “insert”)
- Permissions in
AndroidManifest.xml.
- Analyze Call Graphs: Right-click on a method or field and select “Find usages” or “Go to definition” to understand its context and where it’s called from. This helps in tracing data flow and control flow.
Example: Finding a Potential API Endpoint in Jadx
Let’s say we’re looking for an authentication endpoint. In Jadx, we’d search for terms like “login,” “auth,” or “api/v1”.
// Example Java code found in Jadxpublic class NetworkClient { private static final String BASE_URL = "https://api.example.com/v1/"; private static final String LOGIN_ENDPOINT = BASE_URL + "auth/login"; public String performLogin(String username, String password) { // ... build request with username and password ... HttpResponse response = HttpClient.post(LOGIN_ENDPOINT, requestBody); return response.getBody(); } // ...}
From this, we immediately identify a login endpoint and the associated method, providing a high-level understanding of how authentication might be handled.
Phase 2: Deep Dive into Native Code with Ghidra
Many Android applications utilize native libraries (.so files) written in C/C++ for performance-critical tasks, obfuscation, or platform-specific functionalities. Jadx excels at Java, but for native code, Ghidra is unparalleled. Ghidra provides advanced reverse engineering capabilities, including a powerful decompiler that can translate machine code into pseudo-C.
Step-by-Step Ghidra Workflow:
- Extract Native Libraries: An APK is essentially a ZIP archive. Extract it to a folder (e.g.,
unzip myapp.apk -d myapp_extracted). Native libraries are typically found in thelib/directory, organized by architecture (e.g.,lib/arm64-v8a/libnative.so). - Create a New Ghidra Project:
- Launch Ghidra.
File>New Project>Non-Shared Project. Give it a name and location.File>Import File...and select the.sofile you extracted (e.g.,libnative.so).- Accept default options for import (e.g., processor, language). Ghidra will analyze the file.
- Initial Analysis: After importing, open the file in the CodeBrowser. Ghidra will prompt you to analyze it. Click “Yes” and accept the default analysis options. This step is crucial as it performs symbol recovery, function identification, and cross-referencing.
- Navigate the CodeBrowser:
- Symbol Tree: On the left, expand “Functions” to see identified functions. Look for JNI functions (e.g.,
Java_com_example_app_NativeLib_someMethod) or other interesting exports. - Listing Window: Displays the raw assembly code.
- Decompiler Window: This is Ghidra’s gem. It translates the assembly into pseudo-C code, making native code significantly easier to understand.
- Symbol Tree: On the left, expand “Functions” to see identified functions. Look for JNI functions (e.g.,
- Analyze JNI Functions: JNI (Java Native Interface) functions are key entry points from Java code into native libraries. Their names follow a specific convention (
Java_package_name_ClassName_MethodName). Analyze these functions in the Decompiler window to understand what native operations correspond to Java calls. - Identify Interesting Routines: Look for functions that perform cryptographic operations, manipulate sensitive data, or interact with system features. Search for common library functions (e.g.,
memcpy,strcpy,malloc,free,AES_encrypt,RSA_private_decryptif symbols are present).
Example: Analyzing a JNI Function in Ghidra
Suppose Jadx revealed a Java call to NativeLib.decryptData(byte[] encryptedData). In Ghidra, we’d search for decryptData in the Symbol Tree or specifically look for Java_com_example_app_NativeLib_decryptData.
// Ghidra Decompiler output for a native decrypt functionlong Java_com_example_app_NativeLib_decryptData( JNIEnv *param_1, jobject param_2, jbyteArray param_3) { jbyte *encryptedBytes = (*param_1)->GetByteArrayElements(param_1, param_3, 0); jsize encryptedLen = (*param_1)->GetArrayLength(param_1, param_3); char *key = (char *)decrypt_key_storage_function(); // Call to another function void *decryptedData = (void *)AES_decrypt(encryptedBytes, encryptedLen, key); // Cryptographic call // ... process decryptedData ... (*param_1)->ReleaseByteArrayElements(param_1, param_3, encryptedBytes, 0); return (long)decryptedData;}
This pseudo-C code immediately shows that the native function retrieves a key, calls an AES_decrypt routine, and handles byte array elements. We can then dive deeper into decrypt_key_storage_function or the AES_decrypt implementation if available.
Phase 3: Bridging the Gap – Integrating Jadx and Ghidra Insights
The true power of this workflow lies in combining the high-level Java understanding from Jadx with the low-level native detail from Ghidra. This integrated approach allows for a comprehensive analysis of the entire application.
Workflow Integration Strategy:
- Identify Native Calls in Java: Start in Jadx, looking for classes that load native libraries (e.g.,
System.loadLibrary("nativelib")) and methods declared with thenativekeyword. - Map Java Native Methods to Ghidra Symbols: Once a
nativemethod likepublic native byte[] getKey();is found in Java, formulate its corresponding JNI function name (e.g.,Java_com_example_app_Utils_getKey) and search for it in Ghidra. - Trace Data Flow Across Boundaries: If sensitive data is passed to a native function, follow that data in Ghidra. Similarly, if a native function returns data, observe how it’s used back in Java.
- Correlate Obfuscated Logic: Often, critical logic is split between Java and native layers to complicate analysis. By understanding both, you can reconstruct the full picture, identifying where data transformation, encryption, or integrity checks occur.
For instance, if Jadx shows a Java class calling a native method to “validate license,” and Ghidra reveals that this native method performs a complex cryptographic check against a hardcoded key, you’ve successfully mapped a critical security control. You can then devise a Frida hook or other dynamic analysis techniques to bypass or manipulate this check.
Conclusion
Mastering Android app reverse engineering requires a systematic approach and proficiency with the right tools. By following this workflow – beginning with Jadx for high-level Java analysis and then diving into the native depths with Ghidra – you gain an unparalleled understanding of an application’s inner workings. This combined methodology is essential for identifying vulnerabilities, understanding malware behavior, or simply exploring how applications function at a deeper level. Keep practicing, and you’ll uncover the secrets hidden within Android APKs.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →