Skip to content
All posts
revrevised February 27, 20265 min read

Taking an Android app apart

An APK is a zip, and most of what a mobile challenge turns on is in metadata rather than in code. The manifest rules that decide the attack surface, how to navigate DEX without a decompiler, and when to stop reading and start hooking.

Almost every Android challenge is one of three things: a flag sitting in a resource, a check written in Java that you have to read, or a check written in native code that you have to hook. Working out which one you have takes about two minutes, and doing it in the wrong order costs an hour.

It is a zip, so start with the listing

Unzip it before anything else - an APK is a ZIP, with everything that implies about what else can be hiding in the archive. The file list alone tells you a great deal: how many classes*.dex files there are (multidex means a large app, probably a real one with a library in it), whether there is a lib/ directory (native code, so the interesting logic may not be in Java at all), and what is in assets/ and res/raw/ - which is where a surprising number of challenges simply put the flag.

unzip -l app.apk | sort -k1 -n -r | head -20
# large entries first: the dex, the native libs, and anything unexpected

# Anything in assets that is not what its extension claims
file assets/*
A file in assets/ whose magic does not match its name is a challenge telling you where to look.

The manifest, and the export rule people get wrong

AndroidManifest.xml inside an APK is not XML - it is Android's binary chunk format, and a text editor shows you noise. Decoded, it is the single most informative file in the package, because the *attack surface* of an Android app is defined there and nowhere else.

  • `android:debuggable="true"` - you can attach a debugger or Frida without root. Check this before writing any exploit; it frequently makes the rest of the challenge trivial.
  • `android:allowBackup` defaults to true when absent, which is the opposite default to exported. App data can be pulled with adb backup unless it was deliberately turned off.
  • An exported component with no `android:permission` is reachable by any other app on the device. Count those, and you have measured the surface.
  • `android:usesCleartextTraffic` - not declared means permitted below targetSdk 28 and blocked at or above it, which decides whether an intercepting proxy will see anything.

Full Java decompilation is lovely when it works and frequently does not - obfuscated names, synthetic methods, and Kotlin's generated machinery all produce output that is harder to read than the smali. The good news is that you rarely need it, because the questions a mobile challenge asks are navigational rather than semantic.

  • Where is this string used? A cross-reference from a string constant to its load site lands you in the method that checks it. This is the same reflex as strings plus xrefs on a native binary.
  • Who calls this method? Working backwards from a crypto call or a native method until you reach something the manifest exports gives you the whole reachable path.
  • Which method matters? Rank by call sites. The catch is that the method nothing calls is often the entry point - Android calls it, not the app - so a zero-caller method with a large body is a candidate rather than dead code.

Obfuscated apps make names useless but not the structure. a.a.a() still calls javax.crypto.Cipher.doFinal, and the framework calls are the ones that cannot be renamed. Search for what the app must use rather than for what it chose to call things.

When the answer is not in Java

A lib/arm64-v8a/libnative.so and a System.loadLibrary call means the check has been pushed into C, usually precisely so you have to work for it. That is a normal ELF and everything you know about reversing one applies - with one Android-specific hint: JNI functions are named Java_com_example_Class_method, so the export table tells you which Java method each native function backs.

The signing block is sometimes the challenge

APK signing has three schemes, and which ones are present tells you what repackaging would take. v1 is a JAR signature over individual entries and does not cover the archive structure, so a v1-only APK can be modified in ways verification does not notice. v2 and v3 sign the whole file, which is what makes tampering detectable - and a challenge about repackaging is a challenge about exactly that distinction.

The order, condensed

  1. Unzip and read the file list. Check assets/, res/raw/, and the number of dex files.
  2. Decode the manifest. Find the exported components and the three flags.
  3. Grep the strings. Cross-reference anything interesting to its load site.
  4. Follow calls backwards from the interesting method until you reach an exported entry point.
  5. If the logic is native, treat the .so as an ELF and use the JNI naming to map it back.
  6. If it is debuggable and you have a device, hook it rather than reading it.