← Back to Writeups
CUSTOMMediumSteganography

impossible stego

XESXOR9/10/20263 min read
#steganography#custom#medium#api_transcript_analysis#base64_decoding#custom_extractor_execution#replay_tool_writes#source_tree_reconstruction

impossible stego

Platform: CUSTOM | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: api_transcript_analysis, base64_decoding, custom_extractor_execution, replay_tool_writes, source_tree_reconstruction

Summary

Walkthrough of the CUSTOM Steganography impossible stego. 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 fro

Description

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:

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

Analysis

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:

python
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:

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

Solution

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/:

python
#!/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:

bash
cd recovered_project
python3 -m stego extract ../misc_impossible-stego/flag.png

Output:

text
SEKAI{REDACTED}

Why this works

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.

Key Takeaways / Lessons

  • Mastering api_transcript_analysis, base64_decoding, custom_extractor_execution, replay_tool_writes, source_tree_reconstruction was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-06-28 • CUSTOM • impossible stego • Difficulty: Medium