Battle
Battle
Platform: spbctf | Category: Mobile | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-07-02 | Status: Solved Techniques: base64_xor_string_deobfuscation, dynamic_dex_loading, hidden_dex_in_encrypted_asset, resource_array_password_reconstruction, xor_asset_decryption
Summary
Task: Android APK (com.hackbattle.task) whose password is reconstructed at runtime from resource arrays by a DEX hidden inside an XOR-encrypted asset. Solution: deobfuscate base64+XOR-0xFF strings to find DexClassLoader plan, XOR-0xED decrypt file.bin into file.jar, decompile the hidden DEX, and rebuild the password from R.array.Kappa indices into R.array.Kek.
Battle — spbctf
Description
Battle.apk — find the password that makes the app print "Correct!".
An Android APK where the accepted password is never stored directly. It is
reconstructed at runtime from resource string/integer arrays by a class living
inside a DEX that is itself hidden inside an XOR-encrypted asset and loaded via
DexClassLoader. The goal is to recover the password string.
Analysis
Package: com.hackbattle.task, single MainActivity. Decompile:
bash read-only • copy allowedjadx -d out Battle.apk apktool d Battle.apk -o apktool_out
Notable asset: assets/file.bin (1860 bytes, high entropy → encrypted).
MainActivity.onCreate() obfuscates every string via helper b64e(s) =
base64-decode, then XOR each byte with 0xFF. Decoding the constants reveals
the loader plan:
| Obfuscated constant | Decoded value |
|---|---|
mZaTmtGdlpE= | file.bin (asset to load) |
mZaTmtGVno0= | file.jar (output in getFilesDir()) |
m56TiZaU0YyGjIuaktG7moe8k56MjLOQnpuajQ== | dalvik.system.DexClassLoader |
k5Cem7yTnoyM | loadClass |
nJCS0ZqHnpKPk5rRj56MjIiQjZvRvA== | com.example.password.C |
| |
So the flow is: read assets/file.bin → XOR each byte with 0xED → write to
file.jar → DexClassLoader loads com.example.password.C, then calls:
C.a(Context, R.array.Kappa, R.array.Kek)— builds the expected passwordC.b(String input)— returns"Correct!"on match, else"Wrong!"
Solution
Step 1 — decrypt the hidden DEX (asset XOR key 0xED):
python read-only • copy allowed#!/usr/bin/env python3 data = open('assets/file.bin', 'rb').read() open('file.jar', 'wb').write(bytes(b ^ 0xED for b in data))
file.jar is a ZIP containing classes.dex. Decompile it:
bash read-only • copy allowedjadx -d file_jar_src file.jar
This yields three classes:
A— thin wrapper overResources(getStringArray,getIntArray).B—a(s)= base64-decode + XOR0xFF;b(s1, s2)=s1 + " " + s2 + " Green".C.a()—bb = B.b( B.a(Kek[Kappa[0]]), B.a(Kek[Kappa[1]]) ).
Step 2 — read the resource arrays (apktool_out/res/values/arrays.xml):
integer-array Kappa=[3, 8]string-array Kek= 11 base64+XOR-0xFF blobs. Decoded, they are Kawasaki motorcycle models (thematic to "Battle"):250R, ZZR1400, Z800, Android, ZX-6R, Z1000, ZX-10, KLX110, sup3r, ER-6n, Versys.
Indices Kappa = [3, 8] select Kek[3] = "Android" and Kek[8] = "sup3r".
Step 3 — reconstruct the password using B.b(s1, s2) = s1 + " " + s2 + " Green":
python read-only • copy allowed#!/usr/bin/env python3 import base64 def deob(s): return bytes(b ^ 0xFF for b in base64.b64decode(s)).decode() # Kek array (base64+XOR-0xFF) extracted from res/values/arrays.xml Kek = [ ... ] # 11 blobs Kappa = [3, 8] s1 = deob(Kek[Kappa[0]]) # "Android" s2 = deob(Kek[Kappa[1]]) # "sup3r" print(f"{s1} {s2} Green") # -> REDACTED
Result: Android + " " + sup3r + " Green" = REDACTED.
Entering this in the app's EditText prints Correct!.