logo

Python Challenge - Level 26

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:

  1. Iterate over every byte offset i in mybroken.zip.
  2. Try every possible byte value j in [0, 255].
  3. Calculate the MD5 hash of the mutated buffer.
  4. Stop when the MD5 digest equals bbb8b499a0eef99b52c7f13f4e78c24b.
  5. 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:

http://www.pythonchallenge.com/pc/hex/speedboat.html