Skip to main content

droids3 picoCTF 2019 Solution

Modify and repackage an Android APK to unlock a code path that reveals the flag.

Published: April 2, 2026Updated: August 25, 2026

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.

Download the APK file.

Install apktool and a JDK (needed for keytool and jarsigner or uber-apk-signer). Have an Android emulator running.

bash
wget <url>/three.apk

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Decompile and read the logic
    Observation
    The APK returns 'don't wanna' no matter what you type. That means the logic routes every call to a dead-end method instead of the real flag method, so decompiling will show which call is 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.
    bash
    jadx three.apk -d three_java/
    bash
    cat three_java/sources/com/hellocmu/picoctf/FlagstaffHill.java
    bash
    # Then decompile to smali so you can edit the bytecode:
    bash
    apktool d three.apk --no-res
    What didn't work first

    Tried: Edit the jadx output Java file directly and try to recompile it with javac.

    jadx output is pseudocode for reading, not compilable Java: it lacks correct imports, uses synthetic field names, and does not map back to DEX. apktool works from its own smali representation, so 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 every resource table and can fail on types it cannot reconstruct. The rebuild then produces a malformed APK that adb refuses to install. --no-res skips resource decoding so only the smali 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-res flag skips resource decoding, which avoids errors when rebuilding apps whose resources apktool cannot perfectly reconstruct.

  2. Step 2Patch the smali to call yep instead of nope
    Observation
    jadx shows FlagstaffHill.java has both a nope() returning a static string and a yep() calling the native cilantro() function, and getFlag() only ever calls nope. 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:
    bash
    grep -n 'nope\|yep' three/smali/com/hellocmu/picoctf/FlagstaffHill.smali
    bash
    # Edit the file - replace the nope call with yep:
    bash
    sed -i 's/->nope(Ljava\/lang\/String;)Ljava\/lang\/String;/->yep(Ljava\/lang\/String;)Ljava\/lang\/String;/' three/smali/com/hellocmu/picoctf/FlagstaffHill.smali
    bash
    # Verify the change:
    bash
    grep -n 'nope\|yep' three/smali/com/hellocmu/picoctf/FlagstaffHill.smali
    What didn't work first

    Tried: Rename the nope() method definition to 'yep' instead of editing the call site in getFlag().

    FlagstaffHill already declares yep(Ljava/lang/String;)Ljava/lang/String;, so renaming nope to yep gives the class two methods with the same name and signature and the smali assembler rejects the file during apktool b. Leave both method definitions alone and change only the invoke-static line inside getFlag, which is exactly what the sed command targets: the definition lines have no '->' so the substitution cannot touch them.

    Tried: Edit FlagstaffHill.smali to delete the nope() method body instead of redirecting the call.

    Deleting nope() breaks DEX verification: the class still declares the method in its method table with no implementation behind it, and the package manager rejects the APK at install time. The fix is a one-word substitution in the caller, getFlag, leaving both nope and yep in place.

    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.

  3. Step 3Rebuild, sign, install, and get the flag
    Observation
    apktool strips the original signature when it rebuilds, and Android's package manager will not accept an APK without a valid code-signing certificate. So generate a debug keystore and re-sign before adb install.
    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:
    bash
    apktool b three -o recompiled_three.apk
    bash
    # Create a debug keystore (answer the prompts with anything):
    bash
    keytool -genkey -v -keystore debug.keystore -alias debug -keyalg RSA -keysize 2048 -validity 10000
    bash
    # Sign the APK:
    bash
    jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore debug.keystore recompiled_three.apk debug
    bash
    # Or with uber-apk-signer (simpler):
    bash
    java -jar uber-apk-signer.jar --apks recompiled_three.apk
    bash
    # Install on the running emulator:
    bash
    adb install recompiled_three.apk
    What didn't work first

    Tried: Install the rebuilt APK with adb without signing it first.

    Android's package manager requires a valid JAR signature on every APK. An unsigned one from apktool b gives INSTALL_PARSE_FAILED_NO_CERTIFICATES. Sign with jarsigner or uber-apk-signer using any keystore, even a self-generated debug one, before adb install.

    Tried: Install the signed APK over the existing original app without uninstalling it first.

    If three.apk is already installed, Android compares the update's signing certificate against the original. Your debug keystore is not 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.

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
  • File Magic IdentifierIdentify file types from magic numbers. Paste hex bytes or drop a file to detect PNG, JPEG, ZIP, PDF, ELF, PCAP, SQLite, and dozens of other formats.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.

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.

Key takeaway

Bytecode patching treats an APK as a mutable artifact: apktool disassembles DEX into editable smali, a one-line change redirects control flow, and a rebuild produces a new installable package. That defeats any in-app check whose outcome rests purely on which branch runs, including license checks, feature gates, and root detection. It is standard practice in mobile security research and malware analysis, where the source is never available.

Related reading

Useful tools for Reverse Engineering

Where to go next