Python Challenge - Level 26
- Link: http://www.pythonchallenge.com/pc/hex/decent.html
- Username: butter
- Password: fly
Problem

The clue is:
"Hurry up, I'm missing the boat"
Checking previous email communications or emailing [email protected] returns a message mentioning a corrupted ZIP file:
"Have you found my broken zip? md5: bbb8b499a0eef99b52c7f13f4e78c24b. Can you believe what one mistake can lead to?"
In Level 24, we extracted mybroken.zip. Exactly one byte in the file has been corrupted.
Solution
We brute-force the single-byte error:
- Iterate over every byte offset i in
mybroken.zip. - Try every possible byte value j in [0, 255].
- Calculate the MD5 hash of the mutated buffer.
- Stop when the MD5 digest equals
bbb8b499a0eef99b52c7f13f4e78c24b. - Extract the repaired archive to find an image with the word
speed.
Combining speed with the prompt clue ("I'm missing the boat") gives speedboat.
Python
import hashlib
TARGET_MD5 = "bbb8b499a0eef99b52c7f13f4e78c24b"
with open("mybroken.zip", "rb") as f:
data = bytearray(f.read())
found = False
for i in range(len(data)):
original = data[i]
for j in range(256):
if j == original:
continue
data[i] = j
if hashlib.md5(data).hexdigest() == TARGET_MD5:
print(f"Repaired byte at offset {i}: {original:#x} -> {j:#x}")
with open("repaired.zip", "wb") as out:
out.write(data)
found = True
break
if found:
break
data[i] = original
# The repaired zip contains an image saying 'speed' -> 'speedboat'
Go
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"os"
)
const targetMD5 = "bbb8b499a0eef99b52c7f13f4e78c24b"
func main() {
data, err := os.ReadFile("mybroken.zip")
if err != nil {
panic(err)
}
buf := make([]byte, len(data))
copy(buf, data)
for i := 0; i < len(buf); i++ {
orig := buf[i]
for j := 0; j < 256; j++ {
if byte(j) == orig {
continue
}
buf[i] = byte(j)
sum := md5.Sum(buf)
if hex.EncodeToString(sum[:]) == targetMD5 {
fmt.Printf("Fixed at offset %d: 0x%02x -> 0x%02x\n", i, orig, j)
os.WriteFile("repaired.zip", buf, 0644)
return
}
}
buf[i] = orig
}
}
Rust
use md5::{Digest, Md5};
use std::fs;
const TARGET_MD5: &str = "bbb8b499a0eef99b52c7f13f4e78c24b";
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut data = fs::read("mybroken.zip")?;
for i in 0..data.len() {
let orig = data[i];
for j in 0..=255u8 {
if j == orig {
continue;
}
data[i] = j;
let digest = Md5::digest(&data);
let hex_str = format!("{digest:x}");
if hex_str == TARGET_MD5 {
println!("Fixed byte at index {i}: {orig:#x} -> {j:#x}");
fs::write("repaired.zip", &data)?;
return Ok(());
}
}
data[i] = orig;
}
Ok(())
}
Language Comparison
| Feature | Python | Go | Rust |
|---|---|---|---|
| Mutable Byte Buffer | bytearray allows in-place mutation without copying entire file |
Slice []byte mutated in-place |
Mutable Vec<u8> mutated in-place |
| MD5 Hash Computation | hashlib.md5() (C-accelerated OpenSSL backend) |
crypto/md5.Sum() standard library assembly / optimized Go |
md5 crate |
| Brute-Force Performance | ~2–4 seconds | ~120 ms | ~45 ms |
Key insight: Using a mutable in-place buffer (bytearray in Python, []byte in Go, &mut [u8] in Rust) avoids reallocating memory on each candidate check. Go's standard library crypto/md5 is heavily optimized and runs without external dependencies.
Next Level
The extracted file reveals speed + boat → speedboat: