← Back to Writeups
CUSTOMEasySteganography

Pixelcat

XESXOR9/2/20264 min read
#steganography#custom#easy#bit_plane_analysis#lsb_extraction#single_channel_lsb#linux

Pixelcat

Platform: taipanbyte | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-02-14 | Status: Solved Techniques: bit_plane_analysis, lsb_extraction, single_channel_lsb

Summary

Task: PNG image of a cat, find hidden flag. Solution: LSB steganography extraction from Red channel only (single-channel LSB), standard RGB extraction fails because G/B bits dilute the data.

Pixelcat — TaipanByte CTF

Description

meow

Files: pixelcat.zip -> meow.png (PNG 720x720, RGB)

Image of a cute cat with a rubber duck on its head.

Analysis

Initial Reconnaissance

bash
$ exiftool meow.png
# Standard metadata, sRGB ICC profile, nothing suspicious

$ strings meow.png | grep -i "TB{"
# Nothing found

$ xxd meow.png | tail
# No data after IEND chunk

$ binwalk meow.png
# No embedded files

Standard checks (metadata, strings, appended data, embedded files) yielded nothing.

Bit Plane Analysis

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Analyzed all 8 bit planes for each of the 3 channels (R, G, B):

  • Blue channel bit 7: anomalous ratio 0.3610 (expected ~0.5), but this is just the image structure itself
  • LSB (bit 0) in all channels: distribution close to 0.5 — typical sign of LSB steganography (data looks like random noise)

Key Observation

Uniform LSB distribution (~0.5) in all channels is a classic indicator that data has been written to the least significant bits. However, the flag was only in the Red channel.

Solution

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Approach

Classic LSB steganography, but with a nuance: data is hidden only in one channel (Red), not in all three (RGB). This is important — when extracting from all channels simultaneously, the flag is unreadable because bits from G and B channels "dilute" the useful data.

Algorithm

  1. Read all image pixels row by row (top-left -> bottom-right)
  2. Extract LSB (bit 0) only from Red channel
  3. Assemble bits into bytes (MSB-first, 8 bits per byte)
  4. Search for printable ASCII strings 10+ characters long
  5. Find the flag by pattern TB{...} ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution Code

python
#!/usr/bin/env python3
"""
LSB Steganography Extractor — Single Channel (Red)
Extracts hidden data from LSB of Red channel pixels only.
"""

from PIL import Image
import numpy as np
import re

def extract_red_lsb(image_path, flag_pattern=r'TB\{[^}]+\}'):
    """Extract LSB data from Red channel and search for flag."""
    
    img = Image.open(image_path)
    arr = np.array(img)
    
    # Extract LSB from Red channel only (index 0)
    bits = []
    for y in range(arr.shape[0]):
        for x in range(arr.shape[1]):
            bits.append(arr[y, x, 0] & 1)
    
    # Convert bits to bytes (MSB-first)
    data = bytearray()
    for i in range(0, len(bits) - 7, 8):
        byte = 0
        for j in range(8):
            byte = (byte << 1) | bits[i + j]
        data.append(byte)
    
    # Search for flag
    text = data.decode('latin-1')
    matches = re.findall(flag_pattern, text)
    if matches:
        return matches[0]
    
    # Fallback: find any printable strings 10+ chars
    printable = re.findall(r'[\x20-\x7e]{10,}', text)
    return printable[:5] if printable else None
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

if __name__ == "__main__":
    flag = extract_red_lsb('meow.png')
    print(f"Found flag: {flag}")

Execution

bash
$ python3 solve.py
Found flag: TB{REDACTED}

Why Standard RGB LSB Doesn't Work

python
# Standard approach (all 3 channels) does NOT find the flag:
for pixel in pixels:
    for channel in pixel[:3]:  # R, G, B
        bits.append(channel & 1)

# Because bits from G and B channels are inserted between R channel bits,
# destroying the byte sequence of the flag.
# Need to extract ONLY Red channel.

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Alternative Approaches

bash
# zsteg — automatic analysis, checks different channels
zsteg meow.png

# stegsolve — GUI, allows switching bit planes
java -jar stegsolve.jar

# Quick check of all channels separately:
for ch in 0 1 2:
    python3 -c "
from PIL import Image; import numpy as np, re
arr = np.array(Image.open('meow.png'))
bits = (arr[:,:,$ch] & 1).flatten()
data = bytearray(int(''.join(map(str, bits[i:i+8])), 2) for i in range(0, len(bits)-7, 8))
m = re.findall(rb'TB\{[^}]+\}', data)
if m: print(f'Channel $ch: {m[0]}')
"
done

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Notes

  • When standard LSB (all channels) doesn't work — always check each channel separately
  • Red channel is the most common choice for single-channel LSB stego
  • PNG is required for LSB (JPEG destroys least significant bits during compression)
  • LSB distribution ~0.5 is a strong indicator of hidden data
  • For quick checks use zsteg — it automatically checks all combinations of channels and bits ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍