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.
wget https://artifacts.picoctf.net/c/290/SafeOpener.classstrings SafeOpener.class | grep picojavap -c -p SafeOpenerSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Strings is the fast path
ObservationThe download is a compiled SafeOpener.class, not source. Java class files keep every string literal verbatim in the constant pool as plain UTF-8, so strings plus grep should expose the flag without decompiling anything.Java string constants live in the .class constant pool as plain UTF-8, so strings + grep reveals the flag in one command.bashstrings SafeOpener.class | grep -oE 'picoCTF\{[^"}]*\}'Expected output
picoCTF{SAf3_0p3...8a993}What didn't work first
Tried: Run strings SafeOpener.class without grep, then scan the output manually.
The constant pool holds dozens of JVM-internal strings such as Ljava/lang/String;, Code, and StackMapTable before any user literal shows up. Grepping the picoCTF prefix lands on one line; scrolling the raw output by eye is slow and easy to miss.
Tried: Run file SafeOpener.class or hexdump -C SafeOpener.class to inspect the flag.
file only names the format, and hexdump shows raw bytes without the UTF-8 pass that strings does. The flag bytes are in the dump, but interleaved with pool metadata, so reading them off by eye is unreliable. The printable-run filter in strings is what makes the extraction clean.
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 picohits that table directly.String constants survive obfuscation because the runtime needs them. ProGuard and similar tools rename classes to
a.classand methods toa(), but a literal like"picoCTF{...}"must remain intact forString.equalsto work, so it stays in the constant pool. That is why grepping for the flag prefix beats most light obfuscation.Step 2Peek the constant pool with javap
Observationstrings gives the flag value with no context about which method uses it. javap ships with the JDK, disassembles the bytecode, and annotates each ldc with the constant it loads, so it answers that.javap -c -p prints disassembled bytecode plus the constant pool. No GUI needed and it ships with the JDK.bashjavap -c -p SafeOpenerWhat didn't work first
Tried: Run javap SafeOpener with no flags to look for the flag.
Without -c, javap prints method signatures and no bytecode. Without -p, private members and their constants stay hidden. The flag is loaded inside a private method, so you need both flags to see the ldc that references it.
Tried: Run javap -verbose SafeOpener expecting cleaner flag output than -c -p.
-verbose dumps the whole constant pool in raw index form, which does contain the flag, but it runs for screens. -c -p gives the same value through annotated ldc lines without the class metadata. Both work; -verbose is just slower to scan.
Learn more
javapis the standard JDK disassembler.-cshows method bytecode and-pincludes private members. The output is the closest thing to running "objdump on Java": everyldcinstruction loads a string from the constant pool, and each load is annotated with the literal it resolves to. So scanning the disassembly forldc "picoCTFgives you both the value and the method that uses it.Step 3Optional: decompile to Java source
Observationstrings and javap both show bytecode without the control flow around it. jd-gui rebuilds near-original Java source, which makes the password comparison readable at a glance, and the flag literal falls out of a grep.jd-gui is not in the standard Ubuntu repos, so grab the self-contained jar from its GitHub releases. Once you have the .java file, grep -oE 'picoCTF[^"]*' extracts the literal cleanly.bashwget https://github.com/java-decompiler/jd-gui/releases/download/v1.6.6/jd-gui-1.6.6.jarbashjava -jar jd-gui-1.6.6.jar SafeOpener.classbashgrep -oE 'picoCTF[^"]*' SafeOpener.javaWhat didn't work first
Tried: Open jd-gui and try to save the decompiled source using File > Save All Sources before the GUI fully loads.
jd-gui decompiles lazily: a class is only processed when its tab is opened or when Save All Sources runs. Save too early and the .java file comes out empty or a bare skeleton. Click the class in the tree, wait for the source pane, then save.
Tried: Run grep 'picoCTF' SafeOpener.java without the -oE flag and the character-class stop pattern.
Without -o, grep prints the whole line, including the Java statement wrapped around the flag. That reads fine, but the closing brace and the closing quote sit together, so a copy-paste drags the quote along as a typo. The -oE pattern extracts just the flag token.
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 isgrep -oE 'picoCTF[^"]*' SafeOpener.java:-oprints 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 oldcut -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.
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.
Flag
Reveal flag
picoCTF{SAf3_0p3...8a993}
No dynamic execution is required; the challenge is purely static analysis.