$ cat writeup.md…
$ cat writeup.md…
metactf
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.
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.
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.
"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.
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.
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.
The task gives DawgCTF{Chicken_Sandwich} as a format example: snake_case with an underscore between two words (First_Last).
Final flag: DawgCTF{REDACTED}
curl -O https://metaproblems.com/9158c536955b3b93c3b1ec47841cc0ff/hello.webpdwebp hello.webp -o hello.pngDawgCTF{REDACTED}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.metactf but the flag starts with DawgCTF{.$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar