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/
$ 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
- [crypto][Pro]Поврежденная расшифровка (Corrupted Decryption)— hackerlab
- [reverse][Pro]Cursed Steganography— duckerz
- [crypto][Pro]XORDECRYPT— bluehensctf
- [forensics][Pro]Skeleton— tjctf
- [misc][Pro]Blind Hens— bluehens_ctf_2026