Flash CTF – Rainbow Box

Challenge Description

This challenge presents us with a PNG image file called rainbow_box.png. The challenge involves extracting hidden information from the image using bitplane analysis.

Analysis

The key insight is that this is a bitplane steganography challenge where information is typically hidden in the least significant bits of the RGB channels. In bitplane steganography:

  • Each RGB channel has 8 bits per pixel (0-7, where 0 is LSB and 7 is MSB)
  • Hidden data is typically stored in the lower bitplanes (bits 0-3) to minimize visual impact
  • Binary data can optionally be encoded across multiple bitplanes

In this case, checking the bitplanes using any visual tool will clearly show identifiable characters. For example, the LSB for red shows the character M, the first character of the flag.

Solution

The easiest approach is to use StegOnline, a powerful web-based steganography analysis tool:

  1. Upload the rainbow_box.png file to StegOnline
  2. Navigate to the “Bit Planes” section
  3. Extract the individual bitplanes for each RGB channel, noting a clearly visible character in each bitplane that is not a MSB

StegOnline provides an intuitive interface for bitplane extraction and makes it easy to identify patterns that form readable text. Just browse each bitplane grabbing each character 1 by 1 to solve this way.

Method 2: Custom Python Script

Alternatively, you can write a custom script to extract the bitplanes. Here’s a Python script that extracts all bitplanes except the MSB and displays them in a single row:

#!/usr/bin/env python3
"""
Extract all bitplanes (except MSBs) from a stego image and display them in black and white.
"""

from PIL import Image
import os

def extract_bitplanes_simple(image_path, exclude_msb=True):
    """Extract all bitplanes from an image and save them as individual files."""
    img = Image.open(image_path)
    img_array = img.convert('RGB')
    pixels = img_array.load()
    
    width, height = img_array.size
    output_dir = "extracted_bitplanes"
    os.makedirs(output_dir, exist_ok=True)
    
    channel_names = ['R', 'G', 'B']
    bitplanes_extracted = 0
    
    for channel_idx, channel_name in enumerate(channel_names):
        for bit_idx in range(8):
            if exclude_msb and bit_idx == 7:
                continue
            
            bitplane_img = Image.new('L', (width, height))
            bitplane_pixels = bitplane_img.load()
            
            for y in range(height):
                for x in range(width):
                    pixel = pixels[x, y]
                    channel_value = pixel[channel_idx]
                    bit_value = (channel_value >> bit_idx) & 1
                    bitplane_pixels[x, y] = bit_value * 255
            
            filename = f"{output_dir}/bitplane_{channel_name}{bit_idx}.png"
            bitplane_img.save(filename)
            bitplanes_extracted += 1
            print(f"Saved: {filename}")
    
    print(f"\nExtracted {bitplanes_extracted} bitplanes to '{output_dir}/' directory")
    create_summary_image(output_dir, bitplanes_extracted, width, height)

def create_summary_image(output_dir, num_bitplanes, width, height):
    """Create a summary image showing all bitplanes in a single row."""
    summary_width = width * num_bitplanes
    summary_height = height
    summary_img = Image.new('L', (summary_width, summary_height), 255)
    
    for i in range(num_bitplanes):
        x = i * width
        y = 0
        
        channel_names = ['R', 'G', 'B']
        bit_idx = i % 7
        channel_idx = i // 7
        
        if channel_idx < len(channel_names):
            channel_name = channel_names[channel_idx]
            filename = f"{output_dir}/bitplane_{channel_name}{bit_idx}.png"
            
            try:
                bitplane_img = Image.open(filename)
                summary_img.paste(bitplane_img, (x, y))
            except Exception as e:
                print(f"Warning: Could not load {filename}: {e}")
    
    summary_path = f"{output_dir}/all_bitplanes_summary.png"
    summary_img.save(summary_path)
    print(f"Created summary image: {summary_path}")

if __name__ == "__main__":
    extract_bitplanes_simple("rainbow_box.png", exclude_msb=True)