Reverse engineering an Android app part 1 — Cracking it open

When a client asks us to build an integration against an API that has no public documentation, sometimes the only option is to go straight to the source — the app itself. We recently needed to understand how a particular Android app signs its API requests, and what followed was a proper detective story in three acts. This is act one: getting the source code.

XAPK is just a zip in a trenchcoat

Modern Android apps don’t ship as a single APK anymore. They come as XAPKs — bundles of split APKs, each serving a specific purpose:

graph TD
    A["app.xapk"] -->|unzip| B["base APK — code + resources"]
    A --> C["config.arm64_v8a.apk — native libs"]
    A --> D["config.en.apk — English resources"]
    A --> E["config.xxhdpi.apk — screen density assets"]

The split keeps downloads smaller — your phone only fetches what it needs. For us, it means an extra extraction step before we can get to the code.

# Rename and extract — it's literally a zip
unzip app.xapk -d xapk-extracted/

The base APK is where the interesting bits live. Inside it: 8 DEX files containing roughly 24,744 classes. That’s a lot of haystacks to search through.

Two tools, two perspectives

We reach for two decompilation tools that serve different purposes.

JADX converts DEX bytecode back into readable Java. It’s our go-to for understanding what the code does:

jadx -d output app.apk --show-bad-code -j 4

The --show-bad-code flag is important — when JADX can’t perfectly decompile something, it’ll show its best attempt as a comment rather than silently skipping it.

Apktool decodes resources and produces smali — a human-readable assembly for Dalvik. We need this when we want to modify and rebuild the app later:

apktool d app.apk -o output_dir
graph LR
    A["APK"] --> B["JADX"]
    A --> C["Apktool"]
    B --> D["Java source — for reading"]
    C --> E["Smali + resources — for modifying"]

But then everything’s obfuscated

Here’s what JADX gives us for a typical class:

public final class c {
    private final String a;
    private final String b;
    private final String c;

    public c(String str, String str2, String str3) {
        this.a = str;
        this.b = str2;
        this.c = str3;
    }
}

Single-letter class names, single-letter fields. This is R8 obfuscation at work — Google’s code shrinker renames everything to minimise APK size (and conveniently makes reverse engineering harder). Not all is lost though — string literals, annotation-preserved names, and network DTOs often survive the obfuscation. That’s where we start pulling the thread.

In the next post, we’ll trace through the obfuscated code to find where the app hides its API signing secrets.

Leave a Reply