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.

Android apps SSL Unpinning

Every now and then a client comes to us with an interesting challenge: they need to see what an Android app is actually sending over the wire. Maybe it’s a security audit, maybe they’re debugging an API integration, or maybe they just want to understand what data is leaving their devices. SSL pinning makes this tricky — the app refuses to trust anything other than its own bundled certificate. Here’s how we get around that.

What you’ll need

An Android device — this can be a physical phone, but it doesn’t have to be. Projects like Bliss OS let you run full Android on an x86 virtual machine, which is honestly more convenient for this kind of work — no cables, easy snapshots, and you can throw the VM away when you’re done. We’ve had good results running Bliss OS on Proxmox. Setting up an Android x86 VM is a topic for another day, but it’s worth knowing the option exists.

Software:

  • ADB (Android Debug Bridge)
  • Frida and Objection
  • patch-apk script for repackaging
  • Your proxy’s root certificate installed on the device

Patching the APK

The patch-apk script does the heavy lifting — it decompiles the APK, injects the Frida gadget, and repackages it. Sometimes things won’t build cleanly though. We’ve hit cases where special characters in resource files trip up apktool during reassembly. If that happens, hunt down the offending characters and sanitise them before rebuilding.

Intercepting traffic

Once you’ve got the patched APK installed on the device:

  1. Set up a proxy server with MITM support on your machine — we’ve used Fiddler but mitmproxy works too
  2. Configure the device to use your machine as its proxy
  3. Launch the patched app — it’ll appear stuck on a black screen initially, that’s normal

Now connect to it:

objection explore

Once Objection confirms it’s connected and linked to the instrumented app, the app will unfreeze. Then disable SSL pinning:

android sslpinning disable

Use the app as normal and watch the decrypted traffic flow through your proxy. All the API calls, payloads, headers — everything that was previously hidden behind the pinned certificate is now visible in plain text.