Safe Opener 2 picoCTF 2023 Solution

Published: April 26, 2023

Description

A compiled SafeOpener.class supposedly reveals the forgotten safe code. Either strings analysis or Java decompilation uncovers the embedded flag.

Fast path: strings on the .class file. The flag is in the constant pool as plain UTF-8.

Constant-pool peek without a GUI: javap -c -p SafeOpener.

If you want decompiled Java, jd-gui or jadx renders SafeOpener.java.

bash
wget https://artifacts.picoctf.net/c/290/SafeOpener.class
bash
strings SafeOpener.class | grep pico
bash
javap -c -p SafeOpener
Static recovery from a Java .class is mostly about constant-pool inspection. For Ghidra-style disassembly background see the Ghidra reverse engineering guide; for the broader CLI workflow, the Linux CLI for CTF guide covers grep, strings, and the rest.
  1. Step 1Strings is the fast path
    Java string constants live in the .class constant pool as plain UTF-8, so strings + grep reveals the flag in one command.
    bash
    strings SafeOpener.class | grep -oE 'picoCTF\{[^"}]*\}'
    Learn more

    Java .class files are JVM bytecode. Unlike stripped native binaries, .class files retain a great deal of structure: class names, method names, field names, and string constants all live in the constant pool, a UTF-8 table at the top of the file. strings SafeOpener.class | grep pico hits that table directly.

    String constants survive obfuscation because the runtime needs them. ProGuard and similar tools rename classes to a.class and methods to a(), but a literal like "picoCTF{...}" must remain intact for String.equals to work, so it stays in the constant pool. That is why grepping for the flag prefix beats most light obfuscation.

  2. Step 2Peek the constant pool with javap
    javap -c -p prints disassembled bytecode plus the constant pool. No GUI needed and it ships with the JDK.
    bash
    javap -c -p SafeOpener
    Learn more

    javap is the standard JDK disassembler. -c shows method bytecode and -p includes private members. The output is the closest thing to running "objdump on Java": every ldc instruction loads a string from the constant pool, and each load is annotated with the literal it resolves to. So scanning the disassembly for ldc "picoCTF gives you both the value and the method that uses it.

  3. Step 3Optional: decompile to Java source
    jd-gui or jadx reconstruct SafeOpener.java. Once you have the .java file, grep -oE 'picoCTF[^"]*' extracts the literal cleanly.
    bash
    sudo apt install -y jd-gui
    bash
    jd-gui SafeOpener.class
    bash
    grep -oE 'picoCTF[^"]*' SafeOpener.java
    Learn more

    jd-gui is a graphical Java decompiler that reconstructs near-original Java source from bytecode. Variable names are usually replaced with synthetic identifiers like paramString1, but string literals stay intact, which is all this challenge needs.

    Once you have the decompiled SafeOpener.java, the surgical grep is grep -oE 'picoCTF[^"]*' SafeOpener.java: -o prints only the match (not the whole line) and the character class [^"]* stops at the closing quote of the literal. That is more robust than splitting on backslashes (the old cut -d "\"" recipe), which breaks the moment the source contains escape sequences.

    For heavier lifting, Ghidra handles both .class files and native binaries with one decompiler view, and bytecode-viewer bundles multiple Java decompilers (jd, jadx, CFR, Procyon) so you can compare their output side-by-side when one of them produces garbled control flow.

Flag

picoCTF{SAf3_0p3...8a993}

No dynamic execution is required; the challenge is purely static analysis.

Want more picoCTF 2023 writeups?

Useful tools for Reverse Engineering

Related reading

What to try next