$ cat writeup.md…
$ cat writeup.md…
sekai2026
Task: recover a message hidden with a Claude-generated custom PNG steganography scheme. Solution: decode the provided Claude Code API transcript, reconstruct the generated Python stego source tree from logged Write/Edit tool calls, then run the recovered extractor on flag.png.
i rolled my own stego and made sure to make it wayyyyy too hard for someone to guess how it works...
er, i mean claude did. i totally read the code tho.
The provided archive contains only two files:
misc_impossible-stego/flag.png misc_impossible-stego/messages.log
flag.png is an RGBA PNG image. messages.log is much more important: it is a JSONL log of API requests and responses to an Anthropic/Claude-compatible gateway.
The challenge text says Claude wrote the stego code, so the right first step is not to guess the pixel algorithm. Instead, inspect the log and recover what Claude generated.
Each line of messages.log is a JSON object. The interesting fields are req_body and resp_body; both are base64-encoded. Decoding request bodies reveals Claude Code conversation state, including tool calls such as Write, Edit, Read, and Bash.
A quick parser confirms the late conversation contains the whole source tree creation history:
import json, base64 from pathlib import Path p = Path("misc_impossible-stego/messages.log") last = None for line in p.open(): rec = json.loads(line) s = rec.get("req_body") or "" if not s: continue try: body = json.loads(base64.b64decode(s)) except Exception: continue if "messages" in body: last = body for msg in last["messages"]: for c in msg.get("content") or []: if isinstance(c, dict) and c.get("type") == "tool_use": print(c.get("name"), c.get("input", {}).get("file_path"))
The log shows writes for files like:
stego/secret.py stego/crypto/kdf.py stego/crypto/chacha20.py stego/crypto/csprng.py stego/coding/frame.py stego/image/scatter.py stego/image/embed.py stego/pipeline.py stego/__main__.py
So messages.log effectively leaks the exact steganography implementation.
Reconstruct the final source tree by replaying logged Write and Edit operations. The following script extracts the last full Claude request body, then applies all file writes/edits whose paths are under /home/claude/projects/impossible-stego/:
#!/usr/bin/env python3 import base64 import json import shutil from pathlib import Path log = Path("misc_impossible-stego/messages.log") root = Path("recovered_project") base = "/home/claude/projects/impossible-stego/" body = None for line in log.open(): rec = json.loads(line) s = rec.get("req_body") or "" if not s: continue try: decoded = json.loads(base64.b64decode(s)) except Exception: continue if "messages" in decoded: body = decoded if root.exists(): shutil.rmtree(root) root.mkdir() def local(path): if not path or not path.startswith(base): return None return root / path[len(base):] for msg in body["messages"]: for c in msg.get("content") or []: if not (isinstance(c, dict) and c.get("type") == "tool_use"): continue name = c.get("name") inp = c.get("input") or {} p = local(inp.get("file_path")) if p is None: continue if name == "Write": p.parent.mkdir(parents=True, exist_ok=True) p.write_text(inp.get("content", ""), encoding="utf-8") elif name == "Edit": text = p.read_text(encoding="utf-8") old = inp.get("old_string", "") new = inp.get("new_string", "") if old not in text: raise RuntimeError(f"edit target not found in {p}: {old[:80]!r}") p.write_text(text.replace(old, new, 1), encoding="utf-8") elif name == "MultiEdit": text = p.read_text(encoding="utf-8") for e in inp.get("edits") or []: old = e.get("old_string", "") new = e.get("new_string", "") if old not in text: raise RuntimeError(f"edit target not found in {p}: {old[:80]!r}") text = text.replace(old, new, 1) p.write_text(text, encoding="utf-8") print(f"reconstructed in {root}")
After reconstruction, use the recovered command-line extractor against the challenge image:
cd recovered_project python3 -m stego extract ../misc_impossible-stego/flag.png
Output:
SEKAI{REDACTED}
The custom stego itself is intentionally over-engineered: image-bound key derivation, ChaCha20, HMAC, S-box/whitening, and keyed scatter over RGB LSB slots. However, the security assumption is "the attacker does not know the source".
The supplied log violates that assumption. It records the complete Claude Code session that generated the package, including all source files and later edits. Reconstructing the source gives the exact keys/constants and extraction order, so no steganalysis or brute force is needed.
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar