Note: These are working notes and still in progress.
Introduction
Every Android application has an AndroidManifest.xml. The entry point of the application is the component declared with the android.intent.action.MAIN action in the manifest.
The canonical reference for developing Android applications is developer.android.com. Understanding how an app is meant to be built is what tells you where to push on it when pentesting.
App Components
An Android app is built from four kinds of components: activities, intents, services, and broadcasts. Each is a place where the app receives input, which makes each one a place worth looking at when pentesting.
Activity
An activity is a single, focused thing the user can do. It is what the user interacts with: the UI.
When a new activity is started, it is usually placed on top of the current activity stack. The previous activity stays below it in the stack and does not come back to the foreground until the new activity exits.
An activity moves through four states:
- Active: the activity is in the foreground of the screen.
- Alive: the activity lost focus but is still visible (for example, another activity was launched in a higher position in multi-window mode). It keeps all its state and member information.
- Stopped/hidden: the activity is completely obscured by another activity. It still retains all state and member information, but it is not visible to the user and will often be killed by the system when memory is needed.
- Destroyed: the system drops the activity, either by asking it to finish or by killing its process.
Intents
An intent is a messaging object you use to request an action from another app component. There are three fundamental use cases:
- Starting an activity.
- Starting a service.
- Delivering a broadcast.
Intents come in two types:
- Explicit intents: the exact component (which app, which class) that should satisfy the intent is named.
- Implicit intents: no specific component is named, just a general action. The system decides which component can handle it.
Example of an intent that opens a URL:
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://hextree.io/"));
startActivity(browserIntent);
To receive an intent, you set up the AndroidManifest.xml to accept it (via an intent filter), and the receiving activity must be exported so other apps can reach it.
Services
A service is a component that performs an action in the background, without a user interface.
Broadcast
A broadcast is a message that any application can receive.
Attack Surface
Exported activities are part of the attack surface: any app on the device can interact with the exported parts of an application. Exported services and broadcast receivers are reachable the same way. When mapping an app, the exported components are the first thing to enumerate, because they are what an attacker can touch without any privilege on the app itself.
Reverse Engineering Methodology
How an APK is built
An Android application is written in Java or Kotlin. It is compiled into class files (regular Java bytecode), and a compiler then converts that into DEX code (Dalvik bytecode).
An APK is a ZIP archive containing:
classes.dexAndroidManifest.xml- Resources
- Signature
Some applications will not let you pull their APK off the device. In those cases, search for the APK online, but verify the signature and scan it for malware before trusting it.
Define the goal first
Always define the goal of your reverse engineering before you start. The goal shapes your strategy. For example, if you just want to find where the data comes from (the backend APIs) or to understand the overall app flow, you take a different path than if you are hunting for one specific value, where you may not need to walk the whole app at all.
Comparing app versions
To compare an updated app against an old one, decompile both APKs with jadx into separate folders, then diff the two folders (a VS Code extension works well for this).
Keys hidden in native libraries
Sometimes an app hides keys inside a native library (for example, System.loadLibrary("native-lib")). Native code cannot be decompiled with jadx, so there are three approaches:
- Reverse the binary: use Ghidra to reverse engineer the native library.
- Network interception: intercept the request that uses the key.
- Run the library yourself: build a proof of concept that links the native library and calls it the same way the app does.
- Export the native library binary from the app’s resources.
- Create a new class using the same class, package name, and method names as the application.
- Add code in your PoC that calls the function which exposes the key.
Tooling
The tools you reach for during Android pentesting, and what each is for:
- adb: the bridge to interact with a device or emulator. Install and pull apps, get a shell, read logs, start activities.
- apktool: disassembles an APK into smali (a human-readable form of Dalvik bytecode) and repacks it. Use it when you need to edit and rebuild an app.
- jadx: decompiles an APK back to readable Java. Use it to read the code and search for secrets. It cannot handle native (JNI) code.
- Ghidra: for the native libraries
jadxcannot read.
Quick reference: for the actual commands, see the Android Cheat Sheet.