← Back to Writeups
CUSTOMMediumSteganography

Stereogram

XESXOR9/2/20263 min read
#steganography#custom#medium#autostereogram-pattern-period-#pixel-difference-method-with-p#hidden-3d-message-decoding#linux

Stereogram

Platform: hackerlab.pro | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2025-12-22 | Status: Solved Techniques: Autostereogram pattern period detection via autocorrelation, Pixel difference method with pattern-width shift, Hidden 3D message decoding

Summary

An archive stereogram.zip was provided containing the file stereogram.jpg.

Stereogram — hackerlab.pro

Description

Напрягите свои глаза, чтобы узнать истину =) (Strain your eyes to learn the truth)

An archive stereogram.zip was provided containing the file stereogram.jpg.

Analysis

1. File Investigation

bash
$ file stereogram.jpg
stereogram.jpg: PNG image data, 3664 x 350, 8-bit/color RGB, non-interlaced

The file has a .jpg extension but is actually a PNG image. The dimensions are 3664x350 pixels — a very wide format typical for stereograms.

2. Recognizing the Task Type

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

Key indicators:

  • Task name: "stereogram" — direct hint at the type
  • Description: "Strain your eyes" — classic Magic Eye viewing instruction
  • Image format: Wide horizontal image with repeating pattern
  • Dimensions: 3664x350 — ratio ~10:1, characteristic of autostereograms

3. Autostereogram Theory

An autostereogram (Magic Eye) is an image with a repeating horizontal pattern. The hidden 3D image is encoded through small shifts in the repeating pattern. With proper eye focus (parallel or cross-eyed viewing), the brain interprets these shifts as depth. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Mathematically: if the pattern repeats with period P, then the difference between pixels at distance P from each other reveals the hidden image.

4. Determining the Pattern Period

The pattern period can be determined via autocorrelation:

python
from PIL import Image
import numpy as np

img = Image.open('stereogram.jpg').convert('L')
arr = np.array(img, dtype=np.float64)

# Autocorrelation for the first row
row = arr[arr.shape[0]//2]  # Middle row
correlation = np.correlate(row, row, mode='full')
correlation = correlation[len(correlation)//2:]  # Only positive lags
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Find the first significant peak after zero
# Peak around 171 pixels

Pattern period: ~171 pixels

Solution

Pixel Difference Method

The essence of the method: subtract the image shifted by the pattern period from the original. Areas without hidden content will produce zero difference, while areas with hidden messages will show contrast.

python
#!/usr/bin/env python3
"""
Stereogram decoder using pixel difference method
"""
from PIL import Image
import numpy as np

# Load image in grayscale
img = Image.open('stereogram.jpg').convert('L')
arr = np.array(img, dtype=np.float64)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Pattern period (determined via autocorrelation)
pattern_width = 171

# Calculate difference with shift
# arr[:, pattern_width:] - pixels starting from position pattern_width
# arr[:, :-pattern_width] - pixels up to position (width - pattern_width)
diff = np.abs(arr[:, pattern_width:] - arr[:, :-pattern_width])

# Normalize for better visibility
diff = (diff / diff.max() * 255).astype(np.uint8)

# Save result
Image.fromarray(diff).save('decoded.png')
print("Decoded image saved to decoded.png")

Alternative Method: XOR

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

python
# XOR instead of difference (sometimes gives better results)
arr_int = np.array(img, dtype=np.uint8)
xor_result = arr_int[:, pattern_width:] ^ arr_int[:, :-pattern_width]
Image.fromarray(xor_result).save('decoded_xor.png')

Result

After applying the difference method, the flag text is clearly visible in the image.

Additional Notes

Determining the Pattern Period

If the period is unknown, you can:

  1. Visually: measure the distance between repeating elements
  2. Autocorrelation: find the first significant peak
  3. Bruteforce: try values from 50 to 300 pixels ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
python
# Bruteforce period
for period in range(50, 300, 5):
    diff = np.abs(arr[:, period:] - arr[:, :-period])
    # Save or analyze the result

Flag Irony

The flag REDACTED is an ironic comment about eye strain when viewing stereograms the traditional way. The programmatic decoding method eliminates the need to "strain your eyes".

References