cryptofreemedium

stained-glass

tjctf

Task: 7488-byte binary file encrypted with three per-channel XOR keys plus a residual whole-file XOR layer. Solution: autocorrelation to find key lengths (10/6/14), exploit zero-plaintext region for key recovery, PNG signature known-plaintext for residual key, full decryption reveals flag image.

$ ls tags/ techniques/
known_plaintext_attackper_channel_xor_key_recoveryautocorrelation_key_lengthmulti_layer_xor_decryption

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

stained-glass — TJCTF 2026

Description

three brushes painted over the window

Given a single file window.bin (7488 bytes). The goal is to decrypt it and recover the flag. The hint "three brushes" suggests three layers of encryption applied to a "window" (PNG image).

Analysis

File structure

The 7488-byte file can be viewed as three interleaved channels (byte positions mod 3), each encrypted with a different repeating XOR key. This matches the "three brushes" hint — each brush paints one channel.

Key length determination via autocorrelation

Autocorrelation analysis on each channel revealed distinct repeating key periods:

  • Channel 0 (file positions 0, 3, 6, ...): key period 10
  • Channel 1 (file positions 1, 4, 7, ...): key period 6
  • Channel 2 (file positions 2, 5, 8, ...): key period 14

Zero-plaintext region

A 304-byte region at file positions 160–463 showed extremely low byte diversity per-channel per-key-position — exactly 1 unique value at each (channel, key_position) slot. This indicated the plaintext was zero (null bytes) in that region, meaning the ciphertext bytes there directly reveal the per-channel XOR keys.

Residual XOR layer

After per-channel decryption, the first 8 bytes were 89 FB 6F 57 0F 0A 1A A1 — close to the PNG signature 89 50 4E 47 0D 0A 1A 0A but not exact. XORing the two revealed a period-6 residual key: [0x00, 0xAB, 0x21, 0x10, 0x02, 0x00]. This was confirmed against the IEND chunk at the file's end.

Solution

Step 1: Per-channel key recovery from zero region

The zero region starts at channel index 53 (file position 160 / 3 ≈ 53). Key position offsets must account for 53 % key_length:

#!/usr/bin/env python3 import struct with open("window.bin", "rb") as f: data = bytearray(f.read()) ...

$ grep --similar

Similar writeups