Posted onInctfViews: Word count in article: 298Reading time ≈1 mins.
There's something in the water...
Initial Analysis
The challenge provides a GIF file (CHALL.gif).
Inspecting the file with standard tools reveals a 12-frame
animation.
File Analysis: Running
identify -verbose CHALL.gif shows that the image uses an
8-color Global Color Table (3-bit).
Palette Anomaly: The palette contains redundant
entries for almost identical colors. Specifically, the dark blue color
of the water (11, 41, 71) is mapped to both index
1 and index 3.
Solution
In many steganography challenges involving GIFs or indexed PNGs,
redundant palette indices are used to hide data. I wrote a script to
isolate these two specific indices by extracting the frames and
visualizing the pixels.
#!/usr/bin/env python3 import sys from PIL import Image
iflen(sys.argv) != 2: raise SystemExit("Usage: python palette.py CHALL.gif") with Image.open(sys.argv[1]) as img: img.seek(0) if img.mode != "P": raise ValueError("First frame must retain the original palette indices") palette = img.getpalette() if palette isNoneorlen(palette) < 12: raise ValueError("Missing palette entries 1 and 3") water = [11, 41, 71] if palette[3:6] != water or palette[9:12] != water: raise ValueError("Palette differs from the documented duplicate colors") width, height = img.size indices = list(img.get_flattened_data()) for y inrange(height): row = indices[y * width:(y + 1) * width] print("".join("#"if index == 1else"."for index in row))
Run python palette.py CHALL.gif. The original first
frame is read directly in palette mode, without RGB conversion or
re-quantization. Later GIF frames may be composited into RGB by Pillow
and are outside this extractor. The retained bitmap excerpt records the
proposed flag; CHALL.gif is unavailable, so its palette,
frame count and bitmap have not been rechecked. A synthetic indexed GIF
checks index preservation and image-size bounds.