Eddie Campbell

Home / CV / Contact / Switch to light mode

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:

The bottom puzzle has:

Let’s not wait a moment longer.

And awaaay we go

(⬆ part 3 prompt for tackling the text)

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

top