What Lies Within: LSB Image Steganography Deconstruction

“There's something in the building. Can you retrieve the flag?”
picoCTF{...}💡 THE INTUITIVE ANALOGY (How LSB Works)
Every digital color is represented by numbers from 0 to 255 across Red, Green, and Blue. In binary, 254 is 11111110 and 255 is 11111111. The human eye cannot tell the difference between shade 254 and shade 255. By flipping only the very last bit (the Least Significant Bit) of each pixel, an attacker can secretly store millions of secret binary 1s and 0s directly inside the image without modifying the visible picture!
1. The 5-Step Image Forensics Methodology
Whenever investigating an image in a CTF or incident response investigation, follow this ordered testing pipeline:
[Image File: buildings.png]
│
├── Step 1: Magic Header Check (Verify 89 50 4E 47 ...) ─────────> VALID
├── Step 2: Metadata Inspection (ExifTool author/comments) ───────> CLEAN
├── Step 3: Plaintext Strings (Check appended EOF strings) ───────> NONE
├── Step 4: Signature Carving (Binwalk / Foremost for ZIPs) ──────> NONE
└── Step 5: LSB Steganography (zsteg / StegSolve / PIL) ─────────> 🎯 TARGET IDENTIFIED2. Inspecting Bit-Planes (Aperi'Solve, StegSolve, zsteg)
How to manually extract the secret payload using specialized stego utilities.
1. Navigate to aperisolve.com.
2. Upload buildings.png.
3. Scroll down to the zsteg Analysis section.
4. Notice the detected cleartext string:
Run zsteg to test all permutation combinations (RGB/BGR, MSB/LSB, 1-bit to 8-bit):
Step-by-Step Bit Assembly (How the first 4 characters are born):
| Character | 8 Extracted Bits (R, G, B channels) | Binary to Decimal | ASCII Letter |
|---|---|---|---|
| Byte 0 | 0 1 1 1 0 0 0 0 | 112 | 'p' |
| Byte 1 | 0 1 1 0 1 0 0 1 | 105 | 'i' |
| Byte 2 | 0 1 1 0 0 0 1 1 | 99 | 'c' |
| Byte 3 | 0 1 1 0 1 1 1 1 | 111 | 'o' |
3. Pure Python LSB Bit Decoder (`solve.py`)
Understanding the math behind LSB lets you write custom extractors with zero external stego tools using Python and the standard PIL (Pillow) library:
from PIL import Image
# 1. Load image and raw pixel raster data
img = Image.open('buildings.png')
pixels = img.load()
width, height = img.size
# 2. Extract the Least Significant Bit (LSB) from R, G, and B channels
bits = []
for y in range(height):
for x in range(width):
r, g, b = pixels[x, y][:3]
bits.append(r & 1) # Red channel LSB (0 or 1)
bits.append(g & 1) # Green channel LSB
bits.append(b & 1) # Blue channel LSB
# 3. Assemble consecutive 8 bits into 1 ASCII byte
byte_list = []
for i in range(0, len(bits), 8):
byte_bits = bits[i:i+8]
if len(byte_bits) == 8:
val = 0
for b in byte_bits:
val = (val << 1) | b
byte_list.append(val)
# 4. Decode as string and locate picoCTF flag
extracted_text = bytes(byte_list).decode('latin1', errors='ignore')
if "picoCTF{" in extracted_text:
start_idx = extracted_text.index("picoCTF{")
end_idx = extracted_text.index("}", start_idx) + 1
print("🎉 Extracted Flag:", extracted_text[start_idx:end_idx])python -c "from PIL import Image; img=Image.open('buildings.png'); px=img.load(); w,h=img.size; bits=[c&1 for y in range(h) for x in range(w) for c in px[x,y][:3]]; chars=[chr(int(''.join(map(str,bits[i:i+8])),2)) for i in range(0,len(bits),8)]; txt=''.join(chars); print(txt[txt.find('picoCTF{'):txt.find('}',txt.find('picoCTF{'))+1])"4. Recovered Flag & Verification
Extracted Flag Payload:
(Literal meaning: “hiding in the bits” — a direct reference to LSB stego encoding).
5. Steganography Tool Reference Matrix
| Tool | Platform | Best Use Case |
|---|---|---|
| Aperi'Solve | Web Browser | Automated all-in-one suite running zsteg, steghide, exiftool, and binwalk simultaneously. |
| zsteg | Ruby CLI | Gold-standard command-line tool for detecting LSB encoding in PNG and BMP formats. |
| StegSolve | Java Desktop | Visual plane-by-plane inspection (Red 0, Green 0, Blue 0, Inverted bitplanes). |
6. The Complete Investigation Path & Mental Roadmap
Here is the step-by-step mental roadmap followed to systematically uncover the payload:
Downloaded buildings.png. Ran exiftool and strings to check for easy plain text comments or appended trailers. Results returned clean.
Executed binwalk -e buildings.png to check if another archive or executable was concatenated inside the file. No hidden archives were found.
Uploaded image to Aperi'Solve and ran zsteg buildings.png. The scanner instantly identified ASCII flag text hiding on the 1-bit RGB LSB layer (b1,rgb,lsb,xy).
Wrote a standalone PIL bitmask extractor script to independently verify the bit extraction math without relying on black-box tools.
Recovered the secret flag: picoCTF{h1d1ng_1n_th3_b1t5}.