Eddie Campbell

Home / CV / Contact / Switch to light mode

Puzzle 2 — solve log (day 4, one-shot session)

Target: https://eddiecampbell.uk/puzzle2.html Method: pipeline taught by the six “Articles” posts on eddiecampbell.uk (llm-puzzle-solve-1/2/3 + their READMEs).

Status: layers 1–3 solved, final GPG layer blocked on an unknown passphrase.


TL;DR

Layer Mechanism Result
1 puzzle2.gif → 8 QR frames → base64 → XZ → JPEG ✅ puzzle2.jpg, 571×67 grayscale
2 OCR of the JPEG gives the steghide passphrase ✅ YouCan'tSeeMe@123
3 steghide extracts rdm.txt.xz → grid + figlet + uuencode ✅ all three sub-parts decoded
4 uuencode → XZ → GPG AES256-CFB blob ✅ blob recovered (127 bytes)
5 GPG symmetric decrypt ❌ blocked — passphrase not found

The GPG passphrase was assumed to be the figlet reading WE COULD RULE THE WORLD, following the Part 1/3 write-ups. It does not decrypt the blob in any case/spacing/concat variant tried. That assumption is the single open problem.


Layer 1 — GIF → QR → base64 → XZ → JPEG

puzzle2.html body is just a heading and <img src="puzzle2.gif">.

curl -sO https://eddiecampbell.uk/puzzle2.gif        # 519×519 GIF89a
zbarimg -q puzzle2.gif                               # 8 payloads, all scanned
# payloads carry an "N:" frame-order prefix (same trick as Part 1)
cat payloads.txt | sed 's/^[0-9]*://' | tr -d '\n' > joined.b64
base64 -d joined.b64 > joined.xz                     # 13620 bytes, fd 37 7a 58
xz -dc joined.xz > puzzle2.jpg

Layer 2 — OCR → steghide passphrase

The image contains hand-picked glyphs in a deliberately OCR-hostile font (Part 1 used Chilanka-Regular), so plain tesseract is close but wrong:

tesseract puzzle2.jpg - --psm 7   ->  YouCan'tSeeMe@]23

The ] is a misread 1. Corrected string:

YouCan'tSeeMe@123

Used as the steghide passphrase (Part 1/2 pattern: the visible string is the passphrase):

steghide extract -sf puzzle2.jpg -p "YouCan'tSeeMe@123" -xf rdm.xz -f
xz -dc rdm.xz > rdm.txt

Layer 3 — rdm.txt

Contents (byte-exact, blank line included):

1 cd 2 rl th wrld
205F2728292F5C7C56
hagahahabbbbbaaaaaabbfabbbhaabbbaabbbababbabbbhahbhahahah
hbhagbhgbbbhagbfgbfaahbbbbfagbbbhgbbbhbhaagbbbhgbbdbhbhbe
haaghahfabagagafgafafgbbbagafabagfabbhacbbfabagabbhahahah
hahgaahaabbfgaiaaiafaabbbeahaabbfadbbhahahaabbfahbhbhbhbh
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
abaaabaaaaaaaaaaaaaaaabbbbaaaaaaaaaaaaaaaaaaaaabaaabababa

nx2nx cn b prtty gd
M_3=Z6%H```3FUK1&!,"#`7\A`1P``````````&$+B"H!`'Z,#00)`PH",!H?
M]SQIM?_2;@'E#9'UY^+ORPZX<1=N=1<AA]@J">S_`?04>7-30J7ZAM(>USP)
M^IN9-WI>5L\VXX9?X^Q8UP@Z<A/"I^[<!M9HC?<NCFS%B>3[`@K]-U5M+;5-
MH7!J7@9L>`8#Y%?_\W8VJ4F4J50?3ZSFZ[P,``#1U#IYR'<U+``!GP%_````
0,>E9(;'$9_L"``````196@``

3a. Hex symbol table (line 2)

205F2728292F5C7C56 = ASCII 20 5F 27 28 29 2F 5C 7C 56 = [' ', '_', "'", '(', ')', '/', '\\', '|', 'V']

Nine printable symbols — a subset of the glyphs figlet’s standard font actually emits (Part 3 found the 11-symbol variant for the sibling puzzle). They map positionally to grid letters a–i, one symbol per grid letter in ascending code point.

3b. 6×57 grid (lines 3–8) → figlet art

Decoding a→space, b→_, c→', d→(, e→), f→/, g→\\, h→|, i→V gives a six-row ASCII-art block whose rows are out of order, exactly as described in Part 3. Stored rows decode to:

r0: | \ | | _____      __/ ___|  ___  ___ _ __ ___| |_| | | |
r1: |_| \_|\___| \_/\_/  |____/ \___|\___|_|  \___|\__(_|_|_)
r2: |  \| |/ _ \ \ /\ / /\___ \ / _ \/ __| '__/ _ \ __| | | |
r3: | |\  |  __/\ V  V /  ___) |  __/ (__| | |  __/ |_|_|_|_|
r4: (blank row — all `a`)
r5:  _   _                ____                     _   _ _ _

Correct figlet row order is r5, r0, r1, r2, r3, r4, producing a legible standard-font rendering that reads as “WE COULD RULE THE WORLD” (matching the header hint 1 cd 2 rl th wrld).

nx2nx cn b prtty gd → “Unix to Unix can be pretty good” → uuencode (Part 3 says the same: the intended reading is Unix-to-Unix).

The five M… lines are a uuencoded (not begin-framed) stream. Decoding the uu alphabet by hand/script yields bytes beginning fd 37 7a 58 = XZ.

# 297-byte uu block -> /tmp/p2_uu_fix.txt
python3 uudecode_block.py   # or the inline decoder used in-session
xz -dc p2_uu.xz > data.gpg

Layer 4 — the GPG blob

All three independent routes produced byte-identical output:

md5 fa8443402830d61258073de6f6d82716
- rdm.txt uuencode → XZ → GPG
- /tmp/p2_gpg3.bin
- xz -dc ./data          (local copy in this directory)

127 bytes, and gpg --list-packets reports:

:symkey enc packet: version 4, cipher 9 (AES256), aead 0, s2k 3, hash 10 (SHA512)
    salt 02301A1FF73C69B5, count 65011712 (255)
:encrypted data packet: length: 110, mdc_method: 2

So: AES256-CFB, iterated+salted S2K (S2K count at the maximum, 65,011,712), MDC protected. Standard gpg --symmetric output; nothing exotic.

Layer 5 — BLOCKED: GPG passphrase

Every plausible figlet-derived phrase fails with gpg: decryption failed: Bad session key (identical to the error for a deliberately wrong control string, so the blob itself parses fine — the passphrase is simply wrong).

Tried and failed (exhaustive-ish, not exhaustive):

The figlet text is unambiguous, so the open question is whether the passphrase is not the visible sentence. In Part 1/3 the figlet layer yielded a short opaque token (a!eR/e#) rather than a sentence; the same may be true here, or the figlet art may encode a second, shorter string that was not read.


File inventory

Path What
data local XZ copy of the final GPG blob (layer-4 duplicate)
/tmp/puzzle2.gif original 8-frame QR animation
/tmp/p2_decomp_xz.jpg JPEG after QR→base64→XZ
/tmp/p2_stegout.xz steghide payload
/tmp/p2_rdm_final.txt rdm.txt (grid + figlet + uuencode)
/tmp/p2_uu_fix.txt extracted uuencode block
/tmp/data_dec.bin 127-byte AES256-CFB GPG blob (same as p2_gpg3.bin)

Hashes are recorded above so each stage can be re-verified.

Suggested next moves

  1. Re-read the figlet as a token, not a sentence. Segment the standard-font art glyph-by-glyph (as Part 1 did with the 17 OCR glyphs) and check whether the art is a different string than the obvious sentence — e.g. a deliberately smushed or substituted word.
  2. Attack the S2K hash directly. gpg2john data_dec.bin > gpg.hash then john --wordlist=… gpg.hash (or hashcat -m 17010). With S2K count 65,011,712 this is slow (~seconds/candidate/CPU), so use a tight, context-specific wordlist rather than a general one.
  3. Check the header hint as a transformation, not a translation. 1 cd 2 rl th wrld may be a keyed substitution (numbers as word indices) rather than leetspeak, and the figlet may be the ciphertext of the passphrase.
  4. Re-check OCR of the JPEG at higher fidelity for a second visible string (e.g. trailing characters after @123) — Part 1’s solve hinged on exactly two ambiguous glyphs.