Description
Most-significant-bit steganography hides data in the high bit of each pixel channel. Use a Python MSB steganography script or Stegsolve to extract the hidden text, then grep for the flag.
Setup
Install Stegsolve (or an equivalent tool) and open the provided PNG.
Use Analyse → Data Extract, set bit order to MSB First, and enable Red 7, Green 7, Blue 7. Scroll to the top of the extracted text.
wget https://artifacts.picoctf.net/c/294/Ninja-and-Prince-Genji-Ukiyoe-Utagawa-Kunisada.flag.png# Option 1: Python MSB extraction scriptpip3 install Pillow# Option 2: Stegsolvewget http://www.caesum.com/handbook/Stegsolve.jar -O stegsolve.jarjava -jar stegsolve.jarSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Extract MSB dataObservationThe challenge title 'MSB' directly named the target bit plane, which suggested extracting bit 7 from each pixel channel rather than the more common LSB (bit 0) used in standard steganography tools.Use a Python MSB steganography script to extract bit 7 from each pixel channel. The extracted bytes concatenated form the hidden text. Alternatively, use Stegsolve: toggle Red 7, Green 7, Blue 7 in the data extractor and save the output.pythonpython3 - <<'PY' from PIL import Image img = Image.open("Ninja-and-Prince-Genji-Ukiyoe-Utagawa-Kunisada.flag.png") pixels = list(img.getdata()) bits = [] for pixel in pixels: for channel in pixel[:3]: # R, G, B bits.append((channel >> 7) & 1) # MSB # Group bits into bytes out = bytearray() for i in range(0, len(bits) - 7, 8): byte = 0 for b in bits[i:i+8]: byte = (byte << 1) | b out.append(byte) with open("msb_output.bin", "wb") as f: f.write(out) print("Written msb_output.bin") PYbashstrings msb_output.bin | grep picoExpected output
picoCTF{15_y0ur_que57...d55bee}What didn't work first
Tried: Using steghide or zsteg instead of a MSB-aware script, since the file is a PNG with a hidden message.
steghide only works with password-based LSB embedding in JPEG/BMP and will report 'could not extract any data' on this PNG. zsteg checks LSB planes (bits 0-1 by default) and finds nothing because the flag is in bit 7, not bit 0. Neither tool has a mode for arbitrary bit-plane extraction - the Python script or Stegsolve with 'Red 7, Green 7, Blue 7' checked is required.
Tried: Extracting bit 0 (LSB) instead of bit 7 (MSB) by changing '>> 7' to '>> 0' or '& 1' in the Python script.
LSB extraction produces a different bit stream entirely - one that belongs to the low-order noise in the image. The output will be binary garbage with no printable flag. The challenge title 'MSB' directly names bit 7 as the target; the script must use '(channel >> 7) & 1' to isolate the most significant bit of each channel.
Learn more
In digital images each pixel's color is encoded as a set of channel values (Red, Green, Blue) typically stored as 8-bit integers (0 to 255). Each byte has 8 bit planes: bit 0 is the least significant bit (LSB) and bit 7 is the most significant bit (MSB). Changing bit 7 shifts a channel value by 128, a very visible change. Changing bit 0 shifts it by 1, imperceptible to the human eye.
Classic LSB steganography hides data in bit 0, where changes are invisible. This challenge uses MSB steganography(bit 7) instead. Because the image was specifically chosen to have "natural" high-bit patterns (or was specifically crafted), the flag hidden in the MSB is not visually obvious. Stegsolve's data extractor reads the selected bit from every pixel in row-major order and concatenates them into a byte stream, which surfaces the hidden ASCII text.
Stegsolve is a Java tool that provides bit-plane viewers, color filters, and frame analyzers. It is invaluable for CTF image steganography because it visualizes every bit plane at a glance: a pattern of text in an otherwise noisy bit plane is a dead giveaway that data is hidden there. The full bit-plane workflow is covered in the CTF steganography and steganography tools guides.
Step 2
Search the dumpObservationI noticed the MSB extraction script writes raw binary output to msb_output.bin, which suggested using 'strings' to filter printable sequences before grepping for 'pico', since the flag would be surrounded by non-printable bytes in the bit stream.Stegsolve's data extractor saves the raw bytes (the default save dialog dumps to a file you choose; many people just save it as text in the same directory). Run strings on the saved file and grep for pico. Remove stray spaces from the recovered flag if MSB alignment introduced any.bashstrings text | grep picoExpected output
picoCTF{15_y0ur_que57...d55bee}What didn't work first
Tried: Running 'strings text | grep pico' but using the original PNG file rather than the MSB-extracted output file.
Running strings on the original PNG will scan the raw image bytes - PNG metadata, IDAT compressed pixel data, and ancillary chunks. The flag is not stored as literal ASCII in those bytes; it is encoded bit-by-bit across pixel channels. Only after MSB extraction and byte reconstruction does the flag appear as printable ASCII that strings can find.
Tried: Grepping the extracted file directly without strings, using 'grep pico msb_output.bin' or opening it in a text editor.
grep on a binary file without the -a flag may stop at NUL bytes or misinterpret multi-byte sequences, causing it to silently miss the flag. The extracted MSB stream contains many non-printable bytes surrounding the flag text. Using 'strings' first filters for contiguous printable runs of length >= 4, ensuring the flag's ASCII characters are surfaced cleanly regardless of surrounding binary noise.
Learn more
After extraction, the raw byte stream may contain the flag interleaved with garbage bytes, or the flag may be contiguous.
stringsfilters for sequences of printable ASCII characters of a minimum length (default 4), which naturally isolates human-readable content likepicoCTF{...}from surrounding binary data.The
grep picopipe then narrows the output to only lines containing the flag prefix. This two-step pipeline -strings | grep pattern- is a general-purpose technique applicable to any binary file: ELF binaries, firmware dumps, memory captures, or steg output. It is often the fastest first step in any binary analysis task.About those spaces: MSB extraction reads one bit per channel per pixel, then groups the resulting bit stream into 8-bit bytes. If the hidden message is shorter than the image (or padded with zero-bytes), runs of zero bits decode to NULs (0x00) which print as nothing, and runs of
0x20bytes decode as ASCII space. Strip extra whitespace before submitting.
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{15_y0ur_que57...d55bee}
Only the MSB bits carry useful data, so LSB analysis will pass but yield nothing.