L3akCTF 2025 - Androbro Writeup
L3akCTF 2025 - Androbro Writeup
Time spent: 9 hours 💀
Description
The challenge’s goal is to perform a mix of static analysis and reverse-engineering on an Android APK protected by a native C++ library utilizing layered cryptographic techniques.
The application essentially hides its core logic, and it won’t just hand over the flag. To trigger the payload, users must decrypt an encrypted Dalvik bytecode asset and ultimately recover an AES-encrypted string.
While the challenge authors intended for this to be solved dynamically using Frida, a purely static approach (though grueling at first) proved to be highly effective nonetheless.
Solution
Step 1 & 2: Unpacking and disassembly
Opening the APK and throwing everything into a disassembler immediately sets the stage for a classic reversing journey.
First, we need to decompile the APK using APKLab or jadx and extract the native library located at: lib/x86_64/libragnar.so
Note: The x86_64 version disassembles much more reliably than the ARM architectures.

Loading the library into different disassemblers yields wildly different results:
- BinaryNinja: The output is extremely messy.
- Ghidra: The output is cleaner but unreliable. It struggles to identify the arguments for the
xorbuffersfunction, making it impossible to figure out what is being fed into the cipher. - IDA: Much better. It handles the
x86_64native library without issues, allowing me to continue solving the challenge.
Step 3 & 4: Locating JNI and core logic
From here, we examine the JNI_OnLoad export in IDA to discover the JNI interface functions. We quickly identify a critical function at address 0x68ADE with the following signature:
1 | _JNIEnv *jenv, __int64 a2, __int64 a3, __int64 a4 |

This function seems to drive the core cryptographic workflow, starting at 0x68AC1. By analyzing the operations, we can try to piece together the decryption routine:
- It retrieves the app’s package name (
com.defensys.androbro). - It converts the package name into a Java string and computes a SHA256 digest using Java’s
MessageDigestclass. - The SHA256 digest is converted into a hexadecimal ASCII string. This hex string serves as the key for an RC4 cipher. The input to the cipher is the ASCII bytes of the package name.
- The RC4 output is used as an XOR key to decrypt a hidden asset located at
assets/E/M/O/H/G/CMVASFLW.EXE.
To confirm the XOR key was correct before moving on, I bruteforced the first 8 Dalvik magic bytes (dex\\n035\\0), yielding 6a209693. Decrypting the asset partially confirmed the approach was spot on!
The full required XOR key is: 6a209693a9acaf10dcd2e425bab62a5e48698b7fc3.
Step 5 & 6: Analyzing the DEX and decrypting the flag
The decrypted CMVASFLW.EXE file is actually a Dalvik Executable (DEX).

We can load this decrypted DEX back into jadx. Navigating the structure, we locate the defpackage.FlagChecker class and focus on the checkFlag method.

This method contains the final step: an AES-encrypted flag. The encryption uses AES-CBC with PKCS5 padding. Luckily for us, the Key, IV, and Ciphertext are all hardcoded as Base64 strings right there in the Java class.
Step 7: Capturing the flag
Extracting the Base64 strings and running them through a simple AES decryption script yields the final flag:
L3AK{_Using_native_cpp__is_not_really_hard_xd_31412314}
Notes and alternative approaches
The challenge authors heavily hinted at using Frida to solve this dynamically. The intended route involved:
- Hooking the library (specifically
strcmpfunctions) to print the arguments. - Using
adb shell am broadcastto send specific Intents (THE_TRIGERandTHE_UNLOCKER) containing akeyextra. - Letting the app calculate the correct XOR key and compare it to your input.
However, sometimes static analysis is the only path available, since in my case:
- Frida was severely hindered
frida-gadgetrefused to cooperate on my jailed (non-rooted) Android device- The app completely ignored my ADB broadcasts.
Plus, the cryptography wasn’t complicated enough to force the use of dynamic tools.
I hope this writeup managed to teach you something new! Mobile reverse-engineering challenges are rare, but I always enjoy them, and would love to see more.