timer picoCTF 2023 Solution

Published: April 26, 2023

Description

The TIMER Android APK hides its flag within the Java source. Reverse the application to recover the hard-coded string before the countdown completes.

Install jadx (the easiest path is your package manager; otherwise grab the latest release).

Decompile timer.apk and search the decompiled sources for picoCTF or timer-related strings.

bash
wget https://artifacts.picoctf.net/c/449/timer.apk
bash
# Pick whichever your platform supports:
bash
sudo apt install jadx              # Debian/Ubuntu
bash
brew install jadx                  # macOS
bash
# Or grab the newest build from the releases page (URL in the context).
bash
jadx-gui timer.apk
  1. Step 1Pick the right tool first: jadx
    For nearly every Android CTF the right starting tool is jadx, which decompiles Dalvik bytecode straight to Java. Reach for apktool, dex2jar, or Ghidra only if jadx fails or the app contains native .so libraries.
    Learn more

    APK files are ZIP archives containing compiled Android app code. The code itself is in Dalvik bytecode (inside classes.dex), which is a compact bytecode format for Android's Dalvik/ART virtual machine. Unlike native binaries, Dalvik bytecode decompiles cleanly back to human-readable Java, making Android apps significantly easier to reverse engineer than native C/C++ code.

    Tool ranking when reversing an APK:

    1. jadx first. Highest-fidelity Java decompilation. Try it before anything else. Get it from your distro, brew install jadx, or the jadx releases page.
    2. apktool second. When jadx gives unreadable output, drop down to smali (Dalvik assembly).
    3. dex2jar third. Converts .dex to a regular .jar for the Java tools you already know.
    4. Ghidra last, and only for native .so libraries bundled in lib/.

    For full Ghidra workflow when you do reach a native library, see Ghidra Reverse Engineering.

  2. Step 2Inspect MainActivity and read the flag
    Open MainActivity in jadx-gui. The flag is built character by character inside onCreate, but the literal characters are right there in the decompiled Java.
    Learn more

    MainActivity is always the entry point of an Android app, the first Activity launched when the user opens the app, so it is the natural starting point for analysis. The decompiled code looks roughly like this:

    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            StringBuilder flag = new StringBuilder();
            flag.append('p'); flag.append('i'); flag.append('c');
            flag.append('o'); flag.append('C'); flag.append('T');
            flag.append('F'); flag.append('{');
            flag.append('t'); flag.append('1'); flag.append('m');
            flag.append('3'); flag.append('r'); flag.append('_');
            flag.append('r'); flag.append('3'); /* ... */
            flag.append('}');
            // flag.toString() is never logged, but the characters
            // are all visible in the decompiled source.
        }
    }

    The "hidden" flag is just per-character append() calls. Reading them top-to-bottom and concatenating produces the picoCTF string. No execution required.

    When jadx is not enough: escalate to runtime analysis. If the decompiled output shows reconstruction logic but no plaintext characters (for example, characters loaded from an encrypted byte array, decrypted at runtime, then assembled), you have hit the limits of static analysis. Symptoms include:

    • Strings stored as encrypted byte arrays passed through a decrypt() helper at runtime
    • Reflection-based string lookup (Class.forName(), Method.invoke()) hiding what is actually called
    • Native System.loadLibrary() calls that move flag logic into a .so file
    • String resources fetched only after a server check passes

    That is when you switch to Frida to hook the running app and read values after they are computed in memory. Search Java methods like String.valueOf or the app's own buildFlag() helper, log every argument, and the assembled flag falls out as a side effect.

Flag

picoCTF{t1m3r_r3...496}

No dynamic analysis required, jadx reveals the flag literal when you search for picoCTF.

Want more picoCTF 2023 writeups?

Useful tools for Reverse Engineering

Related reading

What to try next