← Back to Writeups
CUSTOMEasySteganography

Hiding in Plain Sight

XESXOR9/10/20265 min read
#steganography#custom#easy#block_average_downsample#face_identification#gaussian_blur#histogram_equalization#hybrid_image_reveal

Hiding in Plain Sight

Platform: CUSTOM | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: block_average_downsample, face_identification, gaussian_blur, histogram_equalization, hybrid_image_reveal

Summary

Walkthrough of the CUSTOM Steganography Hiding in Plain Sight. AI-generated hybrid image where high-frequency details render a moss-covered Greek statue but low-frequency content hides a portrait of Barack Obama. Solved by Gaussian blur / heavy downsample + histo

Description

There's something strange about this image but I can't put my finger on it, any ideas? The flag will be the name of the person or object you find, in the format DawgCTF{Chicken_Sandwich}.

File: https://metaproblems.com/9158c536955b3b93c3b1ec47841cc0ff/hello.webp

The file is a 1024x1024 lossy WebP showing a detailed classical sculpture: a muscular bearded male figure (Poseidon / Neptune style) covered in green moss, set against a fountain / water background. Note that even though the event is metactf, the flag format is DawgCTF{...} — the challenge was reused from DawgCTF (UMBC). Always follow the format in the task description, not the event name.

Analysis

1. File-level recon — nothing hidden

Standard stego checks all came back empty:

bash
file hello.webp
# RIFF (little-endian) data, Web/P image, VP8 encoding, 1024x1024, lossy

exiftool hello.webp
# Only size/dimensions, no EXIF / XMP / ICC

strings hello.webp | grep -iE 'flag|ctf|dawg|key|secret'
# nothing

Manually parsing WebP chunks confirms a single VP8 chunk with no extra EXIF, XMP , ICCP, ANIM, or ANMF chunks, and no trailing data after the RIFF container:

python
with open('hello.webp', 'rb') as f:
    data = f.read()
assert data[:4] == b'RIFF' and data[8:12] == b'WEBP'
i = 12
while i < len(data):
    cid = data[i:i+4]
    csize = int.from_bytes(data[i+4:i+8], 'little')
    print(cid, csize, i)
    i += 8 + csize + (csize & 1)  # chunk + padding
# VP8  85836 12
# End at 85856 == filesize

So this is not file-level / LSB / metadata stego. The "strange" thing has to be visual.

2. Reading the hint literally

  • Title: Hiding in Plain Sight
  • Description: "There's something strange about this image but I can't put my finger on it"

"Can't put your finger on it" = literally can't see it when you focus on details. That is the textbook description of a hybrid image: an image whose high-frequency content (edges, fine texture) shows one picture, while its low-frequency content (broad tones, coarse shapes) shows a different one. Your brain prefers the high-frequency version when you look closely, so the other picture only shows up when you blur, squint, downscale, or step back.

These are now cheap to produce with AI pipelines like Monster Labs QR-Monster ControlNet and Illusion Diffusion — they take a target "hidden" picture (e.g. a portrait) as a control signal and render a cover image (statue, landscape, …) whose luminance follows the target.

3. Revealing the hidden picture

Convert WebP → PNG for easier processing:

bash
dwebp hello.webp -o hello.png
# 1024x1024, 8-bit RGB

Then attack the image with any low-pass filter. Three approaches that all work:

A. Gaussian blur

python
from PIL import Image, ImageFilter
img = Image.open('hello.png')
for r in [5, 10, 20, 30, 50]:
    img.filter(ImageFilter.GaussianBlur(radius=r)).save(f'hello_blur_{r}.png')

At radius ~25 a portrait clearly emerges in the middle/right of the frame.

B. Heavy downsample + upscale

python
tiny = img.resize((32, 32), Image.LANCZOS)     # or 48/64
big  = tiny.resize((512, 512), Image.LANCZOS)
big.save('hello_pixelated_32.png')

The 32x32 / 48x48 / 64x64 versions show a recognizable face.

C. Block-average downsample + histogram equalization (clearest)

python
import numpy as np
from PIL import Image, ImageOps
arr = np.array(Image.open('hello.png').convert('L'), dtype=np.float32)
factor = 16
h, w = arr.shape
nh, nw = h // factor, w // factor
small = arr[:nh*factor, :nw*factor].reshape(nh, factor, nw, factor).mean(axis=(1, 3))
out = Image.fromarray(small.astype(np.uint8)).resize((1024, 1024), Image.LANCZOS)
ImageOps.equalize(out).save('hello_avg_16_eq.png')

The block-average step throws away all the statue texture, and ImageOps.equalize stretches the contrast of what is left. The result is an unmistakable portrait of a man in a dark suit and tie, with a narrow face, prominent ears and short dark hair.

4. Identification

The distinctive features — short dark hair, narrow face, prominent ears, specific jaw/eyebrow shape, formal suit and tie — match Barack Obama, 44th President of the United States. The underlying "target" image looks like one of his well-known official / campaign portraits.

5. Flag formatting

The task gives DawgCTF{Chicken_Sandwich} as a format example: snake_case with an underscore between two words (First_Last).

Final flag: DawgCTF{REDACTED}

Solution (TL;DR)

  1. curl -O https://metaproblems.com/9158c536955b3b93c3b1ec47841cc0ff/hello.webp
  2. dwebp hello.webp -o hello.png
  3. Blur heavily (Gaussian r=20-40) or downscale to ~32-64 px
  4. Histogram-equalize for contrast
  5. Recognize Barack Obama in the low-frequency content
  6. Submit DawgCTF{REDACTED}

Key takeaways

  • When a stego image task says "look carefully", "step back", "can't put my finger on it", "squint", "from a distance", always try hybrid-image reveal first (Gaussian blur r~25, downscale to ~32-64 px, histogram equalization) before digging into bit planes, LSB, or file-level tricks.
  • numpy.reshape(...).mean(...) block-average downsampling gives a cleaner low-frequency view than a single LANCZOS resize, because it discards the high-frequency cover completely.
  • ImageOps.equalize on the blurred/downsampled result dramatically improves visibility of the hidden portrait.
  • File-level recon is still worth doing first (EXIF, chunks, strings, LSB) — it cheaply rules out the usual suspects before you commit to visual analysis.
  • Flag format is defined by the task description, not the event name. Here the event is metactf but the flag starts with DawgCTF{.

Key Takeaways / Lessons

  • Mastering block_average_downsample, face_identification, gaussian_blur, histogram_equalization, hybrid_image_reveal was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-04-10 • CUSTOM • Hiding in Plain Sight • Difficulty: Easy