Athena Wiki

Android APK Reversing

reversingintermediate

Decompile, analyze, patch, and hook Android APKs in CTF reversing challenges.

reversingandroidapkjadxapktoolfridasmalidalvik

Android APK Reversing

Android APK reversing challenges ask you to extract a flag from an Android application — usually hidden in logic, encoded in resources, or protected by a license check you need to bypass. The good news: APK decompilation with jadx gives near-original Java source code, making Android reversing significantly easier than native binary reversing.


APK Structure

An APK is a ZIP file. Unzip it to see its contents:

unzip app.apk -d app_contents/
ls app_contents/
# AndroidManifest.xml  ← permissions, activities, exported components
# classes.dex          ← compiled Dalvik bytecode (main code)
# classes2.dex         ← additional DEX files (large apps)
# res/                 ← resources (layouts, strings, images)
# assets/              ← raw files bundled with app
# lib/                 ← native .so libraries (arm64-v8a, x86, etc.)
# META-INF/            ← signature files

Step-by-Step Analysis

First Recon

file app.apk
strings app.apk | grep -iE "(flag|ctf|secret|key|password)"

# Check strings.xml (often has hardcoded values)
unzip app.apk res/values/strings.xml -d /tmp/
cat /tmp/res/values/strings.xml | grep -i "flag\|key\|secret"

Decompile with JADX

JADX decompiles DEX bytecode to readable Java:

sudo apt install jadx

# CLI decompilation
jadx app.apk -d ./output/

# GUI (recommended for browsing)
jadx-gui app.apk

In JADX GUI:

  • Navigate to com.example.app.MainActivity
  • Search (Ctrl+F) for: flag, secret, password, key, check, verify
  • Right-click → Find Usage to trace how a variable flows
  • Check AndroidManifest.xml for exported components or unusual permissions

Decompile with apktool (For Smali/Resources)

apktool d app.apk -o app_decoded/

# Key files:
# app_decoded/smali/com/example/app/MainActivity.smali
# app_decoded/res/values/strings.xml
# app_decoded/AndroidManifest.xml

Check native libraries

# If the app has lib/ directory with .so files
file app_decoded/lib/arm64-v8a/libflag.so
# Analyze with Ghidra

Check assets/ for hidden files

ls app_decoded/assets/
strings app_decoded/assets/* | grep -i flag

Understanding Smali

Smali is the intermediate representation of Dalvik bytecode:

# Java: int result = a + b;
# Smali:
add-int v0, v1, v2    # v0 = v1 + v2

# Java: if (flag.equals("CTF{correct}")) { ... }
# Smali:
const-string v1, "CTF{correct}"
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v2
if-eqz v2, :cond_0    # if false, jump to cond_0

Common patterns:

  • if-eqz = if equal zero (false) → jump
  • if-nez = if not equal zero → jump
  • const/4 v0, 0x1 = set v0 to 1 (true)
  • const/4 v0, 0x0 = set v0 to 0 (false)

Patching an APK (Smali Modification)

  1. Find the check in Smali — look for const/4 v0, 0x0 followed by return v0

  2. Change to return trueconst/4 v0, 0x1

  3. Repackage: apktool b app_decoded/ -o app_patched.apk

  4. Sign the APK:

    keytool -genkey -v -keystore my.keystore -alias mykey \
      -keyalg RSA -keysize 2048 -validity 10000
    
    jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
      -keystore my.keystore app_patched.apk mykey
  5. Install: adb install -r app_patched.apk


Dynamic Analysis with Frida

Frida lets you hook and modify app behavior at runtime without modifying the APK.

pip3 install frida-tools

# Start frida-server on device
adb push frida-server /data/local/tmp/
adb shell "chmod +x /data/local/tmp/frida-server && /data/local/tmp/frida-server &"

# List running apps
frida-ps -U

# Hook a function
frida -U -n "com.example.app" -l hook.js

Example Frida script — bypass license check:

Java.perform(() => {
  let MainActivity = Java.use("com.example.app.MainActivity");

  MainActivity.isLicenseValid.implementation = function () {
    console.log("[*] isLicenseValid() hooked → returning true");
    return true;
  };

  MainActivity.checkFlag.implementation = function (input) {
    console.log("[*] checkFlag called with: " + input);
    let result = this.checkFlag(input);
    console.log("[*] Result: " + result);
    return result;
  };
});

Hook native library function:

Interceptor.attach(Module.findExportByName("libflag.so", "verifyFlag"), {
  onEnter(args) {
    console.log("[*] arg0 (string):", args[0].readCString());
  },
  onLeave(retval) {
    retval.replace(1); // force return 1 (success)
  },
});

Common CTF APK Patterns

PatternWhere to LookTool
Hardcoded flagstrings.xml, constant strings in JavaJADX, strings
Flag in logiccheckAnswer(), verifyFlag(), comparisonJADX → trace logic
Encoded flagbase64/XOR in static initializerJADX + CyberChef
Native librarylib/arm64-v8a/*.soGhidra, JADX
Certificate pinning bypassOkHttp, SSLContext classesFrida
Root check bypassisRooted(), RootBeer classFrida

ADB Useful Commands

adb install app.apk
adb pull /data/data/com.example.app/files/flag.txt ./flag.txt
adb shell
adb logcat | grep -i "flag\|secret\|error"
adb forward tcp:8080 tcp:8080

Checklist

  • strings app.apk | grep -i flag (quick win)
  • Check res/values/strings.xml
  • Open in JADX-GUI, search for "flag", "secret", "key", "check"
  • Read AndroidManifest.xml for exported components
  • Check native libraries in lib/ with Ghidra
  • Patch with apktool if logic check needs bypassing
  • Use Frida to hook at runtime (no modification needed)
  • Check assets/ for hidden files

Last updated on

On this page