Description
Milk... Slap... Do the words make sense to you? Download milkslap.png.
Setup
Open the challenge URL in a browser to find the embedded image.
# Open the challenge URL and view source / Network tabSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Discover the embedded image from the pageObservationI noticed the challenge description says 'Download milkslap.png' but links to a web page rather than a direct file, which suggested the PNG is a sub-resource embedded in the page's HTML markup and I needed to inspect the source or Network tab to find its actual URL.Open the page and either view source or open DevTools Network tab. The image URL appears as an <img src="..."> reference (or as a background-image in CSS). Pull it down with wget so you can run tools on the file directly.bashcurl -s <CHALLENGE_URL> | grep -Eo 'src="[^"]+\.png"' | sort -ubashwget <DISCOVERED_PNG_URL> -O milkslap.pngbashfile milkslap.pngExpected output
milkslap.png: PNG image data, 1280 x 720, 8-bit/color RGB, non-interlaced
What didn't work first
Tried: Navigate directly to the challenge URL and assume the page itself is the downloadable PNG, saving the HTML response as milkslap.png.
The challenge URL returns an HTML page, not the image binary. Running file on it would show 'HTML document' and zsteg would refuse to parse it. The actual PNG is loaded as a sub-resource embedded in the page's markup, so you must grep the HTML source for the img src attribute to find the real image URL before downloading.
Tried: Skip the page inspection and guess the PNG URL by appending /milkslap.png to the challenge base URL.
Without reading the page source or checking the DevTools Network tab, the correct sub-resource path is unknown. Guessing common suffixes often returns a 404, and even if the filename happens to match, a different sub-directory path would still produce a failed wget. The grep-based approach in this step extracts the exact src attribute value that the server actually serves.
Learn more
Why scan the page first. picoCTF stego challenges often hide the actual image one or two levels into the HTML rather than in a download link.
view-source:in the browser address bar, orcurl <url>piped throughgrep, reveals every resource the page loads. CSS background-images and lazy-loadeddata-srcattributes are common tricks - check both.Step 2
Run zsteg to detect LSB steganographyObservationI noticed the challenge name 'milkslap' and the image being a plain PNG with no password prompt, which suggested the flag was hidden using LSB steganography inside the pixel data, making zsteg the right tool to automatically scan all bit planes and channel orderings.Use zsteg on the image to automatically detect and extract data hidden in the least significant bits of the image pixels. The flag is hidden using LSB steganography.bashzsteg milkslap.pngbashzsteg -a milkslap.png # exhaustiveExpected output
b1,r,lsb,xy .. text: "picoCTF{imag3_m4n1pul4t10n_sl4p5}"What didn't work first
Tried: Run steghide extract -sf milkslap.png on the image expecting to recover the flag that way.
steghide only supports JPEG and BMP formats - it rejects PNG files with 'steghide: the file format of ... is not supported'. LSB encoding in a PNG is a different technique entirely: steghide uses its own DCT-based embedding scheme, not raw bit-plane manipulation. zsteg is the correct tool because it is built specifically for PNG/BMP LSB analysis across all channel and bit-plane combinations.
Tried: Run strings milkslap.png hoping the flag appears as a plain ASCII run inside the file.
strings scans for contiguous printable ASCII sequences in the raw bytes but LSB steganography distributes each bit of the hidden message across the least significant bit of successive pixel channel bytes. The flag characters are never contiguous in the file - each bit is separated by the remaining 7 bits of the surrounding pixel data - so strings produces no useful output. zsteg reassembles those scattered bits in the correct order.
Learn more
LSB steganography (Least Significant Bit) hides data by replacing the lowest bit of each pixel color channel with a bit of the secret message. Because the change is only 1 out of 255 possible values for each channel, it is virtually invisible to the human eye but detectable with the right tools.
zsteg is a Ruby-based tool specifically designed to detect various steganographic methods in PNG and BMP files. It tries multiple combinations: different bit planes (LSB, bit 1, bit 2...), different color channels (R, G, B, A), different byte orderings, and both row and column scans. Its output shows detected data along with the method used to extract it.
If zsteg is not available, you can use stegsolve (a Java application) to manually examine different bit planes visually. Alternatively,
steghidehandles JPEG and BMP files. For a purely manual approach, a Python script using PIL can extract LSB values:pixel[0] & 1gives the red channel LSB of each pixel.
Interactive tools
- StegallDrop any file and Stegall runs every applicable steg technique in parallel: LSB sweeps, bit planes, spectrograms, polyglot carving, metadata, whitespace decode, and a 6-layer base/ROT/XOR/zlib cascade. Recursively unpacks results and surfaces flag matches.
- Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
Flag
Reveal flag
picoCTF{imag3_m4n1pul4t10n_sl4p5}
The flag was hidden in the least significant bits of the image's pixel data - a classic LSB steganography technique that is imperceptible to the naked eye.