logo
CHEATSHEETSTOOLSABOUT中文

Python Challenge - Level 12

Problem

The image is named evil1.jpg. Probing adjacent filenames reveals:

  • evil2.jpg: Mentions "not jpg, -gfx" → download evil2.gfx.
  • evil3.jpg: Says "no more evils...".
  • evil4.jpg: Contains plain text: "Bert is evil! go back!".

The original image depicts someone dealing cards into 5 stacks. Inspecting evil2.gfx reveals that 5 distinct image streams have been byte-interleaved into a single file.

Solution

De-interleave the binary data into 5 separate output files by routing byte i to file (i % 5).

Python

Python's extended slice syntax data[i::5] makes extracting strided bytes concise:

with open("evil2.gfx", "rb") as f:
    data = f.read()

for i in range(5):
    with open(f"out_{i}.dat", "wb") as out:
        out.write(data[i::5])

Inspecting the generated files reveals image headers for JPEG, PNG, and GIF formats. They display the words dis, pro, port, ional, and an image crossing out ity:

Resulting word: disproportional.

Go

In Go, we read the entire byte slice and distribute bytes into 5 buffers using a single pass:

package main

import (
	"fmt"
	"os"
)

func main() {
	data, err := os.ReadFile("evil2.gfx")
	if err != nil {
		panic(err)
	}

	buffers := make([][]byte, 5)
	for i, b := range data {
		buffers[i%5] = append(buffers[i%5], b)
	}

	for i := 0; i < 5; i++ {
		filename := fmt.Sprintf("out_%d.dat", i)
		if err := os.WriteFile(filename, buffers[i], 0644); err != nil {
			panic(err)
		}
	}
}

Rust

In Rust, pre-allocating byte vectors avoids repeated reallocations:

use std::fs;
use std::io::Write;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = fs::read("evil2.gfx")?;
    let mut streams: Vec<Vec<u8>> = (0..5)
        .map(|_| Vec::with_capacity(data.len() / 5 + 1))
        .collect();

    for (i, &byte) in data.iter().enumerate() {
        streams[i % 5].push(byte);
    }

    for (i, stream) in streams.iter().enumerate() {
        fs::write(format!("out_{i}.dat"), stream)?;
    }

    Ok(())
}

Language Comparison

Feature Python Go Rust
Strided Slicing data[start::step] built-in slice syntax creates a strided sub-slice No strided slicing; requires loop with index increment or modulo .iter().step_by(5) iterator adapter or modulo indexing
Memory Allocations data[i::5] creates 5 full slice copies Single allocation per slice buffer with dynamic growth Single allocation per vector via Vec::with_capacity
Binary I/O Built-in open(..., 'rb'/'wb') Built-in os.ReadFile / os.WriteFile Built-in std::fs::read / std::fs::write
Execution Speed Interpreted slice copying (~2 ms) Native loop compilation (< 1 ms) Native vectorized loop (< 0.5 ms)

Key insight: Python's data[start::step] extended slicing syntax is ideal for rapid interactive hacking. Go and Rust lack extended slice step syntax, but explicit de-interleaving loops with modulo or step iterators are intuitive, type-safe, and give full control over memory allocation.

Next Level

Replace evil with disproportional:

http://www.pythonchallenge.com/pc/return/disproportional.html