Python Challenge - Level 12
- Link: http://www.pythonchallenge.com/pc/return/evil.html
- Username: huge
- Password: file
Problem

The image is named evil1.jpg. Probing adjacent filenames reveals:
evil2.jpg: Mentions "not jpg, -gfx" → downloadevil2.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