Hiding in Plain Sight
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 read-only • copy allowedfile 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 read-only • copy allowedwith 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 read-only • copy alloweddwebp 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 read-only • copy allowedfrom 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 read-only • copy allowedtiny = 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 read-only • copy allowedimport 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)
curl -O https://metaproblems.com/9158c536955b3b93c3b1ec47841cc0ff/hello.webpdwebp hello.webp -o hello.png- Blur heavily (Gaussian r=20-40) or downscale to ~32-64 px
- Histogram-equalize for contrast
- Recognize Barack Obama in the low-frequency content
- 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 singleLANCZOSresize, because it discards the high-frequency cover completely.ImageOps.equalizeon 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
metactfbut the flag starts withDawgCTF{.
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