A Gentle Introduction to Android Reverse Engineering


Android applications are distributed as APK files, but an APK is more than just an executable.

Inside it you’ll find resources, application metadata, native libraries, compiled bytecode, and sometimes surprisingly revealing implementation details.

This makes Android a particularly interesting platform to explore if you want to learn reverse engineering.

You don’t need to start by breaking anything. A great way to learn is to take an application you are allowed to inspect and understand how it works.

What exactly are we reversing?

At a high level, Android applications usually contain several important pieces:

Component Purpose
AndroidManifest.xml Declares application components and permissions
classes.dex Contains compiled Android bytecode
res/ Application resources
assets/ Arbitrary application assets
lib/ Native libraries such as .so files
META-INF/ APK signing and metadata information

An APK is essentially a ZIP archive, so the first step can be as simple as:

unzip application.apk -d application/

After extracting it, you can inspect the directory structure yourself.

application/
├── AndroidManifest.xml
├── classes.dex
├── resources.arsc
├── res/
├── assets/
├── lib/
└── META-INF/

The basic workflow

A typical beginner workflow looks something like this:

        APK


   ┌───────────┐
   │ Extract   │
   └─────┬─────┘


   ┌───────────┐
   │ Inspect   │
   │ Manifest  │
   └─────┬─────┘


   ┌───────────┐
   │ Decompile │
   │ DEX code  │
   └─────┬─────┘


   ┌───────────┐
   │ Analyze   │
   │ behavior  │
   └───────────┘

The important thing is that decompilation is only one part of the process.

A decompiler attempts to reconstruct source code from compiled code. The result is therefore an approximation of what the original developer wrote.

Looking at the manifest

The manifest is one of the first places worth examining.

It tells Android about things such as:

  • Activities
  • Services
  • Broadcast receivers
  • Content providers
  • Permissions
  • Intent filters
  • Application metadata

Modern APKs contain a compiled representation of the manifest, so simply opening it in a text editor may not produce something particularly useful.

Tools such as apktool can decode Android resources into a more readable representation.

apktool d application.apk -o decoded/

You might then find something resembling:

<manifest>
    <uses-permission
        android:name="android.permission.INTERNET" />

    <application
        android:label="@string/app_name">

        <activity
            android:name=".MainActivity"
            android:exported="true" />

    </application>
</manifest>

This immediately gives you a rough map of the application’s externally visible components.

DEX and Dalvik bytecode

Android applications don’t normally execute Java or Kotlin source code directly.

Java and Kotlin are compiled into JVM bytecode and eventually transformed into DEX (Dalvik Executable) format.

A simplified pipeline looks like:

Kotlin / Java


 JVM bytecode


    DEX


 Android Runtime

A DEX file contains classes, methods, fields, strings, and other information required by the runtime.

You can inspect DEX files with tools such as jadx:

jadx application.apk

A successful decompilation might produce something that looks surprisingly close to the original source:

public boolean isDebugEnabled() {
    return this.config.getBoolean("debug", false);
}

But don’t assume the source is necessarily identical to the original.

Names may have been removed or changed, compiler transformations may have occurred, and optimizers can significantly alter the structure of the program.

Obfuscation changes the game

Consider a perfectly readable class:

public class AuthenticationManager {

    public boolean isAuthenticated(User user) {
        return user.getToken() != null;
    }
}

After obfuscation, the same logic could become something closer to:

public class a {

    public boolean a(b c) {
        return c.a() != null;
    }
}

The behavior hasn’t necessarily changed.

The names have.

This is why reverse engineering often involves reconstructing meaning rather than simply reading code.

One useful technique is to start from recognizable strings and work backwards.

"Invalid credentials"


   Search references


   Find method


   Find callers


 Understand data flow

Static vs dynamic analysis

There are two broad approaches to analyzing an application.

Static analysis

Static analysis means examining the application without executing it.

Examples include:

  • Reading decompiled code
  • Examining resources
  • Searching strings
  • Inspecting the manifest
  • Looking at native libraries
  • Following method calls

Static analysis is useful when you want to understand the structure of an application.

Dynamic analysis

Dynamic analysis involves observing the application while it runs.

For example, you might inspect:

adb shell
adb logcat

Or interact with a test application running on an emulator.

Dynamic analysis is useful when static analysis leaves questions unanswered.

For example:

“I found this method, but when is it actually called?”

Runtime instrumentation can help answer that question.

Native code

Not everything inside an APK is Java/Kotlin.

Applications can contain native shared libraries:

lib/
├── arm64-v8a/
│   └── libexample.so
├── armeabi-v7a/
│   └── libexample.so
└── x86_64/
    └── libexample.so

These are typically compiled native binaries.

If you encounter a .so file, the workflow changes considerably.

Instead of looking at DEX bytecode, you might use tools such as:

  • Ghidra
  • IDA
  • radare2
  • objdump
  • strings

For example:

file libexample.so

and:

strings libexample.so | less

can provide some initial clues before opening the binary in a full reverse-engineering tool.

A useful mental model

One of the easiest ways to get overwhelmed by reverse engineering is to treat the entire APK as one giant problem.

Instead, break it into layers:

Layer
Application
UI / Activities / Fragments
Java / Kotlin → DEX
Native libraries → ELF
Android Runtime / Framework
Linux kernel
You don’t need to understand every layer immediately.

Start at the top and move downward whenever the application forces you to.

Building a small lab

The safest way to learn is with applications specifically designed for security education or applications you have explicit permission to analyze.

A basic toolkit might look like this:

Android Emulator

       ├── adb

       ├── apktool

       ├── jadx

       ├── Ghidra

       └── Frida

For example, you can use an emulator rather than experimenting on your personal phone.

That gives you a disposable environment where you can:

  1. Install an APK.
  2. Observe its behavior.
  3. Pull logs.
  4. Inspect files.
  5. Reinstall or reset the emulator.
  6. Repeat the experiment.

What should you learn first?

If you’re completely new to reverse engineering, I’d recommend this progression:

1. Learn Android fundamentals

Understand:

  • Activities
  • Services
  • Intents
  • Processes
  • Permissions
  • APK structure

2. Learn Java/Kotlin bytecode concepts

You don’t need to become a JVM expert.

Just understand how source code eventually becomes something the Android runtime can execute.

3. Get comfortable with JADX

Pick a small application and try to answer simple questions:

Where does the application start?

Which Activity is launched?

Where is this string used?

Which method handles this button?

4. Learn ADB

ADB becomes your bridge between your development machine and an Android device or emulator.

adb devices
adb shell
adb logcat
adb install application.apk

5. Learn native binaries

Once DEX starts feeling comfortable, explore ELF binaries and ARM64 assembly.

This is where tools like Ghidra become particularly useful.

The most important skill

The hardest part of reverse engineering isn’t memorizing commands.

It’s learning how to form hypotheses.

Suppose you find:

String value = preferences.getString("api_url", "");

Don’t immediately start searching the entire application.

Ask:

  1. Where is api_url written?
  2. Who calls this method?
  3. Where does value go?
  4. Is it sent over the network?
  5. Is the value user-controlled?
  6. Does the native layer interact with it?

Each answer gives you another place to investigate.

Reverse engineering is essentially a process of repeatedly narrowing down uncertainty.

Final thoughts

Android reverse engineering sits at the intersection of several areas of computer science:

  • Operating systems
  • Programming languages
  • Compilers
  • Networking
  • Security
  • Assembly
  • Software architecture

That’s what makes it so interesting.

You can start with something as simple as:

jadx app.apk

and eventually end up reading ARM64 instructions inside a native library.

The important part is not trying to understand everything at once.

Pick one APK, ask one question, and follow the evidence.