← Back to Writeups
CUSTOMEasySteganography

Palo Arto

XESXOR9/2/20262 min read
#steganography#custom#easy#base64_image_extraction#palette_index_lsb_steganograph#palette_pair_analysis

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 AColor AIndex BColor BDifference
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:

  1. Extract the base64-encoded PNG from the webpage
  2. Open the image in palette mode (PIL P mode)
  3. Read each pixel's palette index left-to-right, top-to-bottom
  4. Take index & 1 (LSB) as a single bit
  5. Group bits into bytes (MSB-first) to reconstruct the hidden message
python
#!/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}

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍