Palo Arto
Palo Arto
Platform: volgactf | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-03-28 | Status: Solved Techniques: base64_image_extraction, palette_index_lsb_steganography, palette_pair_analysis
Summary
Task: webpage with base64-encoded PNG image using 256-entry palette with near-identical color pairs. Solution: palette index LSB steganography — each pixel's palette index LSB encodes a hidden bit, reading all bits sequentially reveals the flag.
Palo Arto — VolgaCTF 2026
Description
Sometimes artists include hidden messages in their work. Can you find a message in this one?
A webpage at http://paloarto-1.q.2026.volgactf.ru/ contains a large article about color palettes with an embedded base64-encoded PNG image. The task name "Palo Arto" is a wordplay on "Palette Art", hinting at palette-based steganography.
Analysis
The webpage contains a base64-encoded PNG image embedded inline. After extracting it:
- Size: 347KB, 1000×622 pixels
- Mode: 8-bit palette/colormap (P mode) with 256 palette entries
Key discovery: the 256-entry palette is structured as 128 pairs of near-identical colors. Each pair differs by exactly 1 in the Blue channel only — making them visually indistinguishable. For example:
| Index A | Color A | Index B | Color B | Difference |
|---|---|---|---|---|
| 0 | (243, 246, 245) | 181 | (243, 246, 244) | Blue ±1 |
This means each pixel's palette index can be classified as "even" or "odd" within its pair, encoding exactly 1 bit of hidden data without any visible change to the image.
Solution
The steganography scheme uses the LSB of the palette index (not the color values) to encode hidden data:
- Extract the base64-encoded PNG from the webpage
- Open the image in palette mode (PIL
Pmode) - Read each pixel's palette index left-to-right, top-to-bottom
- Take
index & 1(LSB) as a single bit - Group bits into bytes (MSB-first) to reconstruct the hidden message
python read-only • copy allowed#!/usr/bin/env python3 from PIL import Image import numpy as np img = Image.open('image.png') data = np.array(img) flat = data.flatten() # Each pixel's palette index LSB encodes one hidden bit bits = [int(idx) & 1 for idx in flat] # Group bits into bytes (MSB-first) message = [] for i in range(0, len(bits) - 7, 8): byte = 0 for j in range(8): byte = (byte << 1) | bits[i + j] message.append(byte) msg = bytes(message) end = msg.find(b'}') print(msg[:end + 1].decode()) # VolgaCTF{REDACTED}