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.jpgpuzzle2.gifsha2566e4d919d77894dbf4294137d0a3346d11f69bae34ac8bf352fd077332509db98- decoded JPEG sha256
a0f961d53b4373e633c68fa73c1cc0a239950b528d8559fae0e7f816598e60da - JPEG is 571×67, 8-bit grayscale, exactly the shape of Part 1’s answer image (531×67) — i.e. a visible passphrase, not a picture.
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- extracted
rdm.xzsha256325385328e6fbdfd907e0b68a1b8fdaed69cad8614680fcbf7b7d248b9895c9c rdm.txtsha256642c801403a3d00d487d77bc12987eb5805cf42ebd790eacc26ce23ffdc6e795(680 bytes)
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).
3c. Footer (line 9) → uuencode
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.gpgLayer 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):
WE COULD RULE THE WORLD— upper, lower, Title, Sentence- no-space concat
WECOULDRULETHEWORLD,wecouldruletheworld COLDvariants:WECOLDRULETHEWORLD,WE COLD RULE THE WORLD- verb swaps:
CAN / WILL / WOULD / SHOULD / COULDNT / RULED 1 cd 2 rl th wrldliteral,1cd2rlthwrld,1CD2RLTHWRLD,ICD2RLTHWRLD1COULD2RULETHEWORLD,1code2ruletheworld- all 120 permutations of the five words (COULD and COLD bases)
- word subsets (all combinations of WE/COULD/RULE/THE/WORLD)
- leetspeak:
W3C0ULDRUL3TH3W0RLD,WEC0ULDRULETHEW0RLD - separators:
_,-,.,!, trailing/leading space,\n,\r\n - footer-derived:
nextcanbeprettygood,UNIX TO UNIX,UUENCODE, etc. - other-layer secrets:
YouCan'tSeeMe@123,aGt@!!?eeP4565tql,a!eR/e# - puzzle metadata:
puzzle,puzzle2,oneshot,day4,rdm,random,matrix,gpg,aes256 - empty passphrase
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
- 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.
- Attack the S2K hash directly.
gpg2john data_dec.bin > gpg.hashthenjohn --wordlist=… gpg.hash(orhashcat -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. - Check the header hint as a transformation, not a
translation.
1 cd 2 rl th wrldmay be a keyed substitution (numbers as word indices) rather than leetspeak, and the figlet may be the ciphertext of the passphrase. - 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.