Description
The hardest Android challenge. Multiple layers of protection in droids4.apk.
Setup
Download the APK file.
wget <url>/droids4.apkSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Static analysis: decompile and map the appObservationI noticed this was the hardest droids challenge with multiple protection layers described in the prompt, which suggested starting with broad static analysis via jadx to map all obfuscation techniques (name mangling, string assembly, native calls) before attempting any targeted attack.Start with jadx decompilation. Understand the app structure: where is the password validation? Does it involve native code, obfuscated strings, reflection, or runtime class loading?bashjadx droids4.apk -d droids4_java/bashunzip droids4.apk -d droids4_raw/bashls droids4_raw/What didn't work first
Tried: Searching getFlag() for a hardcoded string literal and finding nothing readable
The password is assembled at runtime through character-offset arithmetic: each character is produced by adding an integer offset to a char literal, so no plaintext 'alphabetsoup' appears in the decompiled output. You have to manually evaluate each arithmetic expression in the jadx output and concatenate the resulting characters to reconstruct the password.
Tried: Trying to patch out the password check in smali and skip straight to the flag display
The flag is only produced by the native cardamom() method, which is only called after the correct password is verified. Bypassing the check branch in smali still requires the native call to run with the right internal state, so the flag never appears. The correct approach is to reconstruct the password from the character arithmetic, enter it at runtime, and let cardamom() return the actual flag string.
Learn more
Advanced Android protections may include: ProGuard/R8 obfuscation (class/method name mangling), string encryption (strings decrypted at runtime), reflection (dynamic method calls), DexGuard (commercial obfuscator), and APK split/multi-dex.
Start by finding the entry point (MainActivity), then trace all function calls from the button click handler inward to find the validation logic.
In droids4, the
getFlag()method inFlagstaffHillbuilds a password string through character-offset arithmetic: it adds integer offsets to character literals to assemble each character at runtime. Trace this arithmetic in the decompiled Java to reconstruct the plaintext password:alphabetsoup. Once you have the password, the smali patch replaces thegetFlag()return value with a call to the nativecardamom()method defined inFlagstaffHill, which is what actually produces the flag when the correct password is entered.Step 2
Analyze native librariesObservationI noticed the APK contained a lib/ directory with .so files after unzipping, which suggested the flag validation logic was partially or fully implemented in native JNI code that jadx alone could not fully decompile.Unzip the APK and examine any .so files in the lib/ directory. Load them in Ghidra and look for the JNI entry points. The flag validation may be entirely in native code.bashls droids4_raw/lib/bashfile droids4_raw/lib/x86/*.sobashstrings droids4_raw/lib/x86/*.so | grep -i picoWhat didn't work first
Tried: Running strings on the .so and grepping for 'pico' expecting to find the flag directly
The flag is not stored as a static string inside the native library - it is computed by cardamom() at runtime using values derived from the correct password input. The strings scan shows you the JNI method names and some constants but not the final flag. You need to supply the reconstructed password to trigger the native function and capture its return value.
Tried: Loading only the arm64-v8a .so in Ghidra and not finding the expected JNI exports
Ghidra may load the wrong architecture slice if you pick the wrong lib/ subdirectory. The APK ships separate .so files under lib/x86/, lib/armeabi-v7a/, and lib/arm64-v8a/. The emulator or test device ABI determines which one is actually loaded at runtime. Analyze the slice matching your runtime ABI (typically x86 for AVD emulators) to ensure the function signatures match what you observe with Frida.
Learn more
Even in obfuscated native code, string literals are often stored in plain text in the .rodata section. The
stringscommand extracts printable sequences of 4+ characters from any file, which can quickly reveal hardcoded flags or keys.Step 3
Dynamic analysis with FridaObservationI noticed that the native cardamom() method computes the flag at runtime and static analysis alone could not reveal the final output, which suggested using Frida to hook live method calls and capture the actual flag value returned during execution.If static analysis is insufficient, use Frida to hook key methods at runtime. Hook the final comparison or any method that receives the flag string. Use frida-trace to automatically trace all method calls.bashfrida-trace -U -f com.hellocmu.picoctf -j '*!check*'bashfrida-trace -U -f com.hellocmu.picoctf -j '*!*' -J '*!equals*'What didn't work first
Tried: Attaching frida-trace with -I flag instead of -j to hook Java methods
The -I flag in frida-trace is for including native module patterns (e.g. libc!strcmp), not for Java class method matching. Java method hooks require the -j flag with the 'Class!method' glob syntax. Using -I on a Java class name pattern produces no hooks and no output, silently failing without any error message.
Tried: Spawning frida-trace with -f but seeing 'unable to find process' because frida-server is not running on the device
frida-trace in USB mode (-U) requires frida-server to be running on the Android device or emulator as root (adb shell, then su, then ./frida-server &). Without it, the frida client cannot attach to any process. The alternative is to use the frida gadget embedded in a repackaged APK, but that requires a separate patching workflow.
Learn more
frida-trace automatically generates hook scripts for matched methods and prints their arguments. The
-jflag matches Java methods by class and method name pattern. This is useful when you need to discover which method to hook without reading all the source first.For APK patching: use apktool to decompile, modify the smali to bypass the check or log the expected value, recompile with
apktool b, sign with a debug certificate, and install the patched APK.
Flag
Reveal flag
picoCTF{not.particularly.silly}
Combine static analysis (jadx + Ghidra) with dynamic analysis (Frida hooks) to extract the flag through multiple protection layers.