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

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Pick the right tool first: jadx
    Observation
    I noticed the challenge provided a .apk file, which is an Android application archive containing Dalvik bytecode, and this format decompiles cleanly to Java using jadx, making it the most efficient entry point before reaching for heavier tools like Ghidra or apktool.
    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 2
    Locate the flag in BuildConfig
    Observation
    I noticed that searching for the 'picoCTF' prefix across all decompiled sources is the most direct path to the flag, and because Android's build tools bake constants like VERSION_NAME into the auto-generated BuildConfig class at compile time, a recursive grep after decompilation reliably surfaces hard-coded strings without any runtime execution.
    After decompiling with apktool (or grepping the raw DEX), search for 'picoCTF' in the output. The flag is stored as the VERSION_NAME constant inside BuildConfig.smali, not in any runtime logic in MainActivity.
    bash
    apktool d timer.apk
    bash
    grep -r 'picoCTF' timer/
    bash
    # OR: skip apktool entirely and search the raw bytecode
    bash
    strings classes3.dex | grep picoCTF

    Expected output

    picoCTF{t1m3r_r3v3rs3d_succ355fully_17496}
    What didn't work first

    Tried: Unzip the APK and search the raw classes.dex with grep before running any decompiler.

    grep on the binary .dex file produces no readable output because Dalvik bytecode is not plain text - string literals are encoded inside the DEX string pool. The strings command on classes.dex does surface them, but grepping the decompiled output after running apktool or jadx is more reliable and shows the full class and field context around the match.

    Tried: Search for the flag only inside MainActivity.smali, assuming the countdown timer logic is where the flag is returned.

    MainActivity contains the UI and countdown logic but never references the flag string. The flag is stored in BuildConfig.smali as the VERSION_NAME constant, a separate auto-generated class that the build system populates at compile time. Limiting grep to a single class file misses it entirely; running grep -r across the full decompiled output is necessary.

    Learn more

    Android's build system generates a BuildConfig class at compile time that records build-level constants such as the application ID, build type, and versionName. In this challenge the challenge author placed the flag there as the version name. After apktool decompiles the APK, the class appears as Dalvik assembly at:

    smali_classes3/com/example/timer/BuildConfig.smali

    The relevant section looks like this in smali:

    .field public static final VERSION_NAME:Ljava/lang/String; = "picoCTF{t1m3r_r3v3rs3d_succ355fully_17496}"

    Because the flag is a static string constant compiled directly into the bytecode, no execution is needed. A plain grep or strings search finds it immediately.

    Why apktool here instead of jadx? Both tools work. jadx decompiles to Java and also exposes BuildConfig.VERSION_NAME. apktool produces smali (Dalvik assembly) which is lower-level but keeps every field intact and is easier to grep. For challenges that hide the flag in build metadata rather than runtime logic, either approach reaches the same answer in one search command.

    When static analysis is not enough. If the flag were computed at runtime (encrypted byte arrays, reflection, native libraries), you would need dynamic analysis. That is when you switch to Frida to hook the running app and read values after they are decrypted in memory. This challenge does not require that.

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.
  • 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.
  • 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.

Flag

Reveal flag

picoCTF{t1m3r_r3v3rs3d_succ355fully_17496}

No dynamic analysis required. The flag is stored as the VERSION_NAME constant in BuildConfig.smali and is recovered with a simple grep after decompiling.

Key takeaway

Android APKs are ZIP archives containing Dalvik bytecode that decompiles cleanly to Java, making static analysis far more accessible than with stripped native binaries. Hard-coded secrets embedded as string constants, build metadata fields, or resource values are recoverable without ever running the app, because the bytecode preserves them verbatim. Tools like jadx and apktool expose the full class hierarchy and every constant in a searchable text form. The same principle applies to any managed-runtime binary (Java JARs, .NET assemblies, Python pyc files): bytecode rarely hides as much as developers assume.

Related reading

Want more picoCTF 2023 writeups?

Useful tools for Reverse Engineering

What to try next