Solving a puzzle with OpenCode - part 3
Getting here
We started off Part 2
with a .jpg image that depicted a known string, and
through a series of prompting were able to guide OpenCode toward
using a steganography tool to extract some hidden information
from it. The hidden information in this case was an
.xz file that decompressed into a plain text
file.
In this article we will use OpenCode to unlock the secrets within!
The file
Before commencing, let’s see what we are working with:
$ cat rdm.txt
1 cd 2 rl th wrld
2028292C2E2F3C5C5F607C
afaijakakfaiahakicakaafafaiahiaaeeaaik
ahiidibichiiikikahihfifahiiikakikkikaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaiiaikakaiiikaaiahaaaafafiiaikakkakia
kabikakikaaiifaaiagaafafaaiifiaaaaaaik
aaaaaaaiaaaaaaiiiiaaaaaaiiaaaaaiaaiaaa
nx2nx cn b prtty gd
M_3=Z6%H```3FUK1&`@`A`1P````0SUC,`0"*C`T$"0,"(-;4(8/M&&'_TGH!
M8Y0U?B5F1\1FL2V?S8D5F)47W>Q7AK'9*B$3?>&/J&8G([=$U,\X:N^=&4S`
M3FD,@TIE68T0UGT'P?:H-G5/(\I;@CZJ$L@JLV85\=^(&MDFH>8B9MC#Y!MC
MGX=!$E\S:`G)&>LVF(Z-,RMT4;_%GJQ`YK-Q=!J,]@``I:C*6+!-W8(``:,!
4BP$``"#%)UJQQ&?[`@`````$65H`
Two distinct looking puzzles that are linked in some way.
The top puzzle has:
- A human readable clue
- A line of hex characters
- A blob of text containing the range of letters [a-k]
The bottom puzzle has:
- A different human readable clue
- A block of characters from the printable ASCII range.
Let’s not wait a moment longer.
And awaaay we go
Quick solves
Within seconds there were strong indicators that the model had got to the crux of the top puzzle. Snippets of Python code flew by with:
hexs="2028292C2E2F3C5C5F607C"
and then:
symbols: [' ', '(', ')', ',', '.', '/', '<', '\\', '_', '`', '|'] 11
/ _` | |/ _ \ |_) | / / _ \_ .. _|
\__,_(_)\___|_| \_\/_/ \___| |_||_|
__ _| | ___| _ \ / /__ _| || |_
| (_| |_| __/ _ < / / __/_ _|
_ ____ __ _ _
What I’d hoped would be an interesting challenge for a human was no match for the model.
The line of hex was correctly identified as pairs of ASCII codes, positionally mapping to the letters a-z in the following blob. Following this decipher, we are left with a block of “lines and angles” from punctation marks.
It was the next part that really impressed me:
$ which figlet toilet; python3 -c "import PIL; print(PIL.__version__)"
/usr/bin/figlet
12.1.1
The “lines and angles” were ASCII art characters.
figlet
A fun and easy way to generate ASCII art is by using
figlet. If you have ever used the tools
k9s for Kubernetes, then you may have noticed its
logo; likely generated using the “Graffiti” font.
$ figlet -f Graffiti "K9s"
____ __.________
| |/ _/ __ \______
| < \____ / ___/
| | \ / /\___ \
|____|__ \ /____//____ >
\/ \/
Lines and lines and lines
There was a twist however in our blob of ASCII art characters - the lines were all out of order:
/ _` | |/ _ \ |_) | / / _ \_ .. _|
\__,_(_)\___|_| \_\/_/ \___| |_||_|
__ _| | ___| _ \ / /__ _| || |_
| (_| |_| __/ _ < / / __/_ _|
_ ____ __ _ _
The fact that the model was still able to infer that it was
figlet output was really surprised me - I had
anticipated clues being required here.
The model immediately made moves to building up a context of what the various figlet fonts looked like:
No image support; decoding textually. Let me identify the figlet font and decode the ASCII art programmatically.
$ for f in standard small mini lean slant shadow block banner digital mnemonic script term smslant smshadow smscript bubble big; do echo "=== $f ==="; figlet -f $f -w 200 -k aq 2>/dev/null | cat -A | head -8; done
=== standard ===
$
__ _ __ _ $
/ _` | / _` |$
| (_| || (_| |$
\__,_| \__, |$
|_|$
=== small ===
$
__ _ __ _ $
Followed by building up a character set:
$ for c in a b c d e f g h i j k l m n o p q r s t u v w x y z; do echo "== $c"; figlet -f small -w 200 -k $c 2>/dev/null; done
== a
__ _
/ _` |
\__,_|
And then a word list:
words=["hello","world","qwerty","puzzle","password","passcode",
I was unsure whether the lines had already been shuffled into the correct order until:
The art rows look like a permutation of figlet standard-font rows. Let me reorder to native figlet row order and segment letters precisely.
Crashing out
It was going so well, and then the model decided it wanted permission to go looking for more information:
Let me check the surrounding directory — there may be source files or a README with puzzle instructions I haven't seen.
I denied permission to go searching other directories on my machine as in the previous part, and the solve ceased.
Interview questions
I wanted to see how much was understood at this point and
asked what do you know so far; the response:
The file (15 lines)
- L11–15: uuencode → 200-byte XZ → 139-byte GPG AES256-symmetric blob. Decoded, saved (/tmp/opencode/out.raw) — needs a passphrase to decrypt.
- L2: hex 2028292C2E2F3C5C5F607C = [' ','(',')',',','.','/','<','\\','_','','|'] — exactly the glyph‑charset of figlet's *standard* font; ordered → a–k`.
- L3–8: 6 rows × 38 cols of that alphabet (a=space). Row 5 (all a) is blank. This is an ASCII-art figure.
- L1 1 cd 2 rl th wrld and L10 nx2nx cn b prtty gd are textspeak hints (roughly "…rule the world…" / "next to next can be pretty good"), not yet used.
Despite not mentioning it up until now, the output here
suggests that the bottom puzzle had been figured out trivially.
The intent with the nx2nx cn b prtty gd clue for
that part would be to guide a human toward “Unix-to-Unix” or
uuencode (“Unix to Unix can be pretty good”).
Going by the incorrect interpretation of the human readable
clue, the model will have used a more technical approach, and
was able to uudecode the contents into a separate
file. A file that needed a gpg passhrase to
view!
To get an idea of whether the shuffled ASCII art containing
the required passphrase was solved I asked
what are the letters in the figlet and part of the
output displayed:
0123456789012345678901234567890123456789
0000000000111111111122222222223333333333
0 _ ____ __ _ _
1 __ _| | ___| _ \ / /__ _| || |_
2 / _` | |/ _ \ |_) | / / _ \_ .. _|
3 | (_| |_| __/ _ < / / __/_ _|
4 \__,_(_)\___|_| \_\/_/ \___| |_||_|
5
Which shows that the correct ordering had been solved.
Creating figlet output is a one way street, and so
“reading” the symbols would need some kind of comparison engine
- at least for a computer.
And this appeared to be the direction of travel in the
activity triggered by the prompt. Further output suggested that
the first 3 and final symbols had been identified
a!e...#, but there were struggles obtaining the
R. References to “smushing” checks for pairs of
characters may have been the culprit here - there’s certainly an
overlap shown in the eR.
OK. Deep breath. Let me reconsider EVERYTHING about the art more carefully. Let me actually just LOOK at the art in figlet-ish form one more time and try reading simple words:
I felt maybe best to give the model some space at this point…
Time passes; beards grow
Returning to my laptop saw that solving had ceased once again. Several context compactions had occurred during the considerable period where things were left alone. I believe that these compactions contributed to some tangents that were evident when scrolling back.
One such tangent involved the misinterpretation of the human readable clue.
What if "next to next" means each symbol must be shifted to its neighbor in the alphabet table (a Caesar shift over the 11 symbols)? Let me test symbol-shifts before re-rendering.
There was an emphasis on dictionary searches for the missing symbols too, rather than attempting to interpret them as they were.
Bonus xkcd reference:
cands = ['p4ssw0rd','password123','correcthorsebatterystaple','letmein','admin','123456','qwerty','monkey']
Ultimately, there was another request to go snooping for clues which was the cause of the brakes being pumped.
All smush-modes × perms × a-words → nothing. Let me step back and compare with day2 — same puzzle family, likely built with the same trick. Reading day2's files should reveal the intended construction.
Clue time again
I gave a targeted clue, revealing the R,
there are 7 characters in the figlet... first 4 are "a!eR" and last is "#". Find the remaining two characters.
We have a winner and the final “Our Princess is in another castle!” style message was revealed.
The puzzle was finally solved!
The GPG passphrase is `a!eR/e#` (the 7-char string: a,!,e,R,/,e,#). Using it decrypts `/tmp/opencode/out.raw` to give "well done p1 complete
instructions for p2 would go here".
The figlet art corresponds to that 7-character string rendered in standard figlet.
Seeing a “todo” style message is perhaps an unrewarding end to the trail, but sometimes life is like that. I asked for a write-up README as in the past, but could see that the timestamps were not at all accurate - as before I attributed this to context compaction.
Take aways and a fresh challenge
There were some parts of this solve that were really exciting to see, and I could definitely feel my heart rate increase watching the attempts to reverse match the ASCII art output to strings.
I was surprised at how little ceremony there was for the
uudecode section, although this is possibly tainted
by my own trials: I vividly remember struggling to dutifully
paste together uuencoded content spread across
several emails from a friend in 1997 (which would eventually
reveal an .mp3 of “Twig” by
POTUSA).
As with part 2 it did feel that there were times that the model was being led by the nose at times, however we ended up at the correct solution - and crucially - with LLM-generated documentation on how we got there.
Speaking of which: is there enough information in these
articles and their associated README files to build
enough of a context to solve the puzzle end-to-end in a single
prompt?
In the next part OpenCode will attempt a newly generated puzzle containing different passphrases.
If you think your coding agent is a match, then the link to the new puzzle is here (contains flashing images).