Hiding in Plain Sight
Hiding in Plain Sight
Platform: Metactf | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-10 | Status: Solved Techniques: block_average_downsample, face_identification, gaussian_blur, histogram_equalization, hybrid_image_reveal
Summary
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 + histogram equalization to reveal the hidden face.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
metactf| ID:20260410_metactf_hiding_in_plain_sight - Tags: steganography, image, hybrid_image, optical_illusion, webp, blur, downsample, histogram_equalization, image_identification, ai_generated
- Indicators: Task name: Hiding in Plain Sight, Hint: 'can't put my finger on it' (stop looking at details, look at the whole), 1024x1024 WebP image, single VP8 chunk, no EXIF / XMP / hidden chunks, Visible content: highly detailed moss-covered classical sculpture / Poseidon-style figure, No LSB / file-level steganography present
- Source:
20260410_metactf_hiding_in_plain_sight.md
Foothold
Vulnerability / Misconfiguration
- Block_average_downsample
- Face_identification
- Gaussian_blur
- Histogram_equalization
- Hybrid_image_reveal
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- block_average_downsample
- face_identification
- gaussian_blur
- histogram_equalization
- hybrid_image_reveal
- Tags: steganography, image, hybrid_image, optical_illusion, webp, blur, downsample, histogram_equalization, image_identification, ai_generated
Original Writeup
<details><summary>Click to expand original content</summary>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:
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:
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:
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
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
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)
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)
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{.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR