$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: a PyInstaller-packed Windows binary leads to a password-protected ZIP and a deliberately corrupted ELF checker. Solution: recover embedded Python secrets, derive the PNG-based hint, repair every-10th-byte corruption, and solve the final rolling-sum constraints.
Provided files:
maze.png,enc_maze.zip, andmaze.exe.
English summary: The challenge starts from a PyInstaller-packed Windows executable and leads through several nested stages. The goal is to recover hidden secrets, unpack the protected archive, repair the corrupted Linux checker, and solve its final validation routine.
This was a staged reverse engineering challenge:
maze.exe.maze from enc_maze.zip.The downloaded archive contained:
rev_maze/maze.pngrev_maze/enc_maze.ziprev_maze/maze.exeQuick triage showed that maze.exe was a Windows PE produced with PyInstaller. That immediately suggested two useful directions:
.pyc modules instead of treating the PE as a normal native crackme.The nested enc_maze.zip indicated a multi-stage design, and the presence of maze.png hinted that image data might later be reused as part of a key or seed derivation step.
The executable was unpacked as a PyInstaller archive and the bundled Python bytecode was extracted from the embedded PYZ. Disassembly/decompilation of the recovered modules exposed the real control flow.
Important recovered modules included:
obf_path,This stage converted the challenge from “reverse a packed PE” into “read Python logic and recover staged constants.” That was the first big simplification.
Reading the recovered Python logic revealed two critical hardcoded values:
Y0u_St1ll_1N_4_M4z3enc_maze.zip: Y0u_Ar3_W4lkiNG_t0_Y0uR_D34THThose constants were enough to move to the next stage and extract the inner file named maze from the protected ZIP.
obf_path and the PNG-derived seed hintThe obf_path helper contained another important clue. It derived a seed from selected bytes inside maze.png:
seed = maze_png[4817] + maze_png[2624] + maze_png[2640] + maze_png[2720]
For the supplied file this evaluates to:
493
The helper then printed the effective hint:
seed(493) for i in range(300): randint(32,125)
This was useful for understanding the challenge structure: the image was not just decoration, but part of the obfuscated guidance. It also confirmed that the author wanted the solver to follow the bytecode breadcrumbs instead of brute forcing later stages.
After unzipping enc_maze.zip, the inner file maze looked like an ELF, but was intentionally broken. The key observation was that corruption occurred at absolute file offsets divisible by 10.
That explained why the file looked “almost right” while still breaking headers, strings, code, and data.
Two additional observations mattered:
So the real challenge was not cryptography. It was selective binary repair.
The practical repair approach was:
Because every 10th byte could be wrong, both disassembly and constants had to be validated manually instead of trusted blindly.
Once the relevant parts of the checker were repaired, the final validation model became clear:
In other words, for bytes x[i] the checker enforced constraints of the form:
x[i] + x[i+1] + x[i+2] == table[i]
After repairing the relevant table values and reconstructing the validation loop, the remaining task was straightforward constraint solving. Solving the repaired system produced the final flag:
HTB{REDACTED}
The full solve path was:
maze.exe as a PyInstaller binary.Y0u_St1ll_1N_4_M4z3 and Y0u_Ar3_W4lkiNG_t0_Y0uR_D34TH.maze file.obf_path to derive the seed(493) hint from maze.png bytes.10.#!/usr/bin/env python3 """ Reproduction helper for the recovered logic. This script documents the important constants and the final checker model. """ from pathlib import Path import random FLAG = "HTB{REDACTED}" FIRST_PATH = "Y0u_St1ll_1N_4_M4z3" ZIP_PASSWORD = "Y0u_Ar3_W4lkiNG_t0_Y0uR_D34TH" def derive_seed(png_path: str) -> int: data = Path(png_path).read_bytes() return data[4817] + data[2624] + data[2640] + data[2720] def rolling3(buf: bytes): return [buf[i] + buf[i + 1] + buf[i + 2] for i in range(len(buf) - 2)] def main(): png = "maze.png" if Path(png).exists(): seed = derive_seed(png) print(f"Recovered seed: {seed}") random.seed(seed) sample = ''.join(chr(random.randint(32, 125)) for _ in range(16)) print(f"Hint stream preview: {sample}") print(f"Recovered first path string: {FIRST_PATH}") print(f"Recovered ZIP password: {ZIP_PASSWORD}") print(f"Candidate length: {len(FLAG)}") print("Rolling 3-byte sums of final solution:") print(rolling3(FLAG.encode())) print(f"Flag: {FLAG}") if __name__ == "__main__": main()
HTB{REDACTED}
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar