Description
The app always returns 'don't wanna' no matter what you type. Fix that by editing the bytecode directly: decompile the APK with apktool, swap one method call in the smali, rebuild, sign, and install the patched APK.
Setup
Download the APK file.
Install apktool and a JDK (needed for keytool and jarsigner or uber-apk-signer). Have an Android emulator running.
wget <url>/three.apkSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Decompile and read the logicObservationI noticed the APK always returned 'don't wanna' regardless of input, which suggested the app's logic was routing all calls to a dead-end method rather than the real flag method, so I needed to decompile it to locate exactly which method call was responsible.Decompile with jadx for readability. In FlagstaffHill.java you will see that getFlag() calls nope(input), which returns the string 'don't wanna'. There is a second method yep(input) that calls the native cilantro() function and returns the real flag, but getFlag() never reaches it. The fix is to redirect that one method call from nope to yep.bashjadx three.apk -d three_java/bashcat three_java/sources/com/hellocmu/picoctf/FlagstaffHill.javabash# Then decompile to smali so you can edit the bytecode:bashapktool d three.apk --no-resWhat didn't work first
Tried: Edit the jadx output Java file directly and try to recompile it with javac.
jadx produces pseudocode for human reading only - it is not valid, compilable Java. jadx output lacks correct imports, uses synthetic field names, and does not map back to DEX. apktool works on its own smali representation; editing jadx output changes nothing in the APK.
Tried: Run apktool without --no-res and proceed when it throws resource decode errors.
Without --no-res, apktool tries to decode all resource tables and may fail on certain resource types it cannot reconstruct. The subsequent rebuild step then produces a malformed APK that adb refuses to install. The --no-res flag skips resource decoding so only the smali (bytecode) side is touched.
Learn more
Android apps ship as DEX bytecode, not Java source. jadx converts DEX back to readable Java pseudocode - great for analysis. apktool converts DEX to smali, a human-readable assembly language that maps one-to-one with DEX opcodes and can be rebuilt into a valid APK. You need smali (not jadx output) for the edit because apktool can only round-trip its own smali representation.
The
--no-resflag skips resource decoding, which avoids errors when rebuilding apps whose resources apktool cannot perfectly reconstruct.Step 2
Patch the smali to call yep instead of nopeObservationI noticed from the jadx decompilation that FlagstaffHill.java contained both a nope() method returning a static string and a yep() method calling the native cilantro() function, and getFlag() only called nope, which suggested a single invoke-static substitution in the smali would redirect execution to the real flag.Open three/smali/com/hellocmu/picoctf/FlagstaffHill.smali and find the getFlag() method. You will see an invoke-static instruction that calls nope. Change the word 'nope' to 'yep' in that one line. Everything else in the file stays the same.bash# View the method in the smali file:bashgrep -n 'nope\|yep' three/smali/com/hellocmu/picoctf/FlagstaffHill.smalibash# Edit the file - replace the nope call with yep:bashsed -i 's/->nope(Ljava\/lang\/String;)Ljava\/lang\/String;/->yep(Ljava\/lang\/String;)Ljava\/lang\/String;/' three/smali/com/hellocmu/picoctf/FlagstaffHill.smalibash# Verify the change:bashgrep -n 'nope\|yep' three/smali/com/hellocmu/picoctf/FlagstaffHill.smaliWhat didn't work first
Tried: Change only the method name in the invoke-static line to 'yep' but leave the descriptor as '->nope(...)' elsewhere in the file.
smali resolves the full method reference including the method name inside the descriptor string. If any part of the invoke-static line still says 'nope', the DEX verifier sees a call to a method that does not exist and the app crashes with a NoSuchMethodError at runtime. The sed command replaces the full descriptor so the entire reference is consistent.
Tried: Edit FlagstaffHill.smali to delete the nope() method body instead of redirecting the call.
Deleting the nope() method causes a DEX verification failure because the class still declares the method in its method table but its implementation is missing. The package manager rejects the APK at install time. The correct fix is a one-word substitution in the caller (getFlag), leaving both nope and yep intact.
Learn more
The smali line before the edit looks like:
invoke-static {p1}, Lcom/hellocmu/picoctf/FlagstaffHill;->nope(Ljava/lang/String;)Ljava/lang/String;After the edit it reads:
invoke-static {p1}, Lcom/hellocmu/picoctf/FlagstaffHill;->yep(Ljava/lang/String;)Ljava/lang/String;The type descriptor
Ljava/lang/String;uses smali syntax: L for a reference type, slashes instead of dots, and a semicolon terminator. You do not change those because nope and yep have identical signatures.Step 3
Rebuild, sign, install, and get the flagObservationI noticed that apktool strips the original signature during rebuild and Android's package manager requires a valid code-signing certificate to accept any APK, which meant I had to generate a debug keystore and re-sign before adb install would succeed.Rebuild the patched APK with apktool, create a debug signing keystore with keytool, sign the new APK with jarsigner (or uber-apk-signer), install it on the emulator, and press the button. Because getFlag() now calls yep() instead of nope(), the app returns the flag string on screen.bash# Rebuild:bashapktool b three -o recompiled_three.apkbash# Create a debug keystore (answer the prompts with anything):bashkeytool -genkey -v -keystore debug.keystore -alias debug -keyalg RSA -keysize 2048 -validity 10000bash# Sign the APK:bashjarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore debug.keystore recompiled_three.apk debugbash# Or with uber-apk-signer (simpler):bashjava -jar uber-apk-signer.jar --apks recompiled_three.apkbash# Install on the running emulator:bashadb install recompiled_three.apkWhat didn't work first
Tried: Install the rebuilt APK with adb without signing it first.
Android's package manager requires every APK to have a valid JAR signature before installation. An unsigned APK from apktool b produces an INSTALL_PARSE_FAILED_NO_CERTIFICATES error from adb. You must sign with jarsigner or uber-apk-signer using any keystore - even a self-generated debug one - before adb install will succeed.
Tried: Install the signed APK over the existing original app without uninstalling it first.
If the original three.apk is already installed, Android compares the signing certificate of the update against the original. Your debug keystore has a different certificate than the original release key, so adb returns INSTALL_FAILED_UPDATE_INCOMPATIBLE. Run 'adb uninstall com.hellocmu.picoctf' first, then install the patched APK fresh.
Learn more
Every APK installed on Android must carry a valid code-signing certificate. When you rebuild with apktool the original signature is stripped, so you must re-sign before installation. A self-signed debug certificate is fine for an emulator - it just needs to be consistent across installs (so uninstall the original app first if it is already on the emulator).
Once installed, open the app and press the Flag button. The patched getFlag() now calls yep(), which internally calls the native
cilantro()function compiled into the APK, and the flag appears on screen.
Flag
Reveal flag
picoCTF{tis.but.a.scratch}
The flag is revealed by the patched app on screen after pressing the button - no network call needed, the native cilantro() function embedded in the APK computes it locally.