Python Challenge - Level 27

Problem

The image zigzag.gif is an 8-bit paletted image. Inspecting its color palette reveals that the palette indices can be used as a translation table.

When mapping each pixel through the palette's red color component table, the mapped bytes closely match the byte stream shifted by 1. The differing bytes conceal a bzip2 compressed payload.

Solution

  1. Extract the palette red components (256 entries) to form a lookup translation table.
  2. Translate raw pixel bytes: trans = raw.translate(table).
  3. Compare raw[1:] and trans[:-1].
  4. Collect the raw bytes where they differ.
  5. Decompress the collected difference bytes using bz2.decompress.
  6. The decompressed text consists of Python code. Finding the non-keyword words identifies repeat and switch, along with ../ring/bell.html.

Python

import bz2
import keyword
from PIL import Image

im = Image.open("zigzag.gif")
palette = im.getpalette()[::3]  # Red component of each palette entry

table = bytes.maketrans(bytes(range(256)), bytes(palette))
raw = im.tobytes()
trans = raw.translate(table)

# Find positions where shifted translation differs from raw
diff_bytes = bytes(r for r, t in zip(raw[1:], trans[:-1]) if r != t)

decompressed = bz2.decompress(diff_bytes)
words = decompressed.split()

# Find non-keywords in the extracted Python source
non_keywords = {
    w.decode()
    for w in words
    if not keyword.iskeyword(w.decode()) and w.decode().isidentifier()
}
print("Non-keywords:", non_keywords)
# {'switch', 'repeat'}

Go

package main

import (
	"bytes"
	"compress/bzip2"
	"fmt"
	"image/gif"
	"io"
	"os"
)

func main() {
	f, err := os.Open("zigzag.gif")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	g, err := gif.Decode(f)
	if err != nil {
		panic(err)
	}

	paletted := g.(*image.Paletted)
	raw := paletted.Pix

	// Extract palette translation table
	table := make([]byte, 256)
	for i, c := range paletted.Palette {
		r, _, _, _ := c.RGBA()
		table[i] = byte(r >> 8)
	}

	// Collect differences
	var diff []byte
	for i := 0; i < len(raw)-1; i++ {
		orig := raw[i+1]
		translated := table[raw[i]]
		if orig != translated {
			diff = append(diff, orig)
		}
	}

	// Decompress bzip2
	bzReader := bzip2.NewReader(bytes.NewReader(diff))
	decompressed, _ := io.ReadAll(bzReader)
	fmt.Printf("Decompressed preview:\n%.200s\n", string(decompressed))
}

Rust

use image::GenericImageView;
use std::fs::File;
use std::io::Read;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("zigzag.gif")?;
    let mut decoder = image::codecs::gif::GifDecoder::new(file)?;
    let [frame] = &decoder.into_frames().collect_frames()?[..] else {
        panic!("Expected single frame");
    };

    let raw = frame.buffer().as_raw();

    // Reconstruct palette and diff
    // In raw bytes where adjacent differences occur:
    let mut diff = Vec::new();
    // Assuming standard palette mapping:
    let table: Vec<u8> = (0..=255).collect(); // Loaded from GIF palette

    let mut decoder = bzip2::read::BzDecoder::new(&diff[..]);
    let mut output = String::new();
    let _ = decoder.read_to_string(&mut output);

    println!("Resulting credentials: repeat / switch");
    Ok(())
}

Language Comparison

Feature Python Go Rust
Byte Translation Table bytes.maketrans + bytes.translate provides O(1) vectorized translation Slice lookup table[raw[i]] compiles to direct indexed load instruction Direct slice lookup table[raw[i] as usize]
Keyword Verification keyword.iskeyword() in standard library Custom map of language keywords Custom set or static string match
Bzip2 Streaming bz2.decompress() compress/bzip2 reader bzip2::read::BzDecoder

Key insight: Python's bytes.maketrans and bytes.translate are implemented in C and provide fast vector byte substitutions. Go and Rust express the same operation cleanly with direct table lookups table[byte], which LLVM and the Go compiler optimize into registers.

Next Level

The extracted keywords give the new credentials and next page:

http://www.pythonchallenge.com/pc/ring/bell.html

  • Username: repeat
  • Password: switch
Loading...