Zip, Zip, Hooray!
Zip, Zip, Hooray!
Platform: broncoctf2026 | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-07-11 | Status: Solved Techniques: password_pattern_discovery, recursive_archive_extraction, scripted_multi_format_extraction
Summary
Task: a mislabeled challenge.zip is actually a 1250-layer deep chain of recursively nested archives (gzip -> tar -> bzip2 -> password-protected 7z -> zip -> repeat). Solution: script 7z as a universal driver to list+extract each layer, deriving each 7z password from the first member's filename, filtering the tar @PaxHeader artifact, and recursing until flag.txt.
Zip, Zip, Hooray! — broncoctf2026
Description
"I was trying to compress my files and my script got a little carried away... Can you help me find my original file?"
Hint: the 7z files are password protected, and the password is the name of the first file inside.
Given a single challenge.zip downloaded from the CTFd instance. Goal: recover the original file (which holds the flag) from a runaway recursive compression chain.
Analysis
Despite the .zip extension, file challenge.zip reports gzip-compressed data (was "layer1.tar"), so the extension is misleading — this is a matryoshka-style nested archive.
The chain cycles the same compression pattern once per "round", repeating for hundreds of rounds:
gzip (.tar.gz) -> tar (.tar) -> bzip2 (.bz2) -> 7z (password-protected AES) -> zip -> (back to gzip)
Two gotchas per round:
- tar
@PaxHeader: tar extraction emits an extra@PaxHeaderpseudo-file that must be filtered so the "single next file" logic keeps working. - 7z password: each 7z layer is AES-encrypted. Per the hint, the password for a 7z archive is the full basename (with extension) of its first contained member. E.g. the 7z named
layer290containslayer292.zip, so its password islayer292.zip.
7z handles gzip, bzip2, tar, zip and 7z transparently, so a single loop using 7z as a universal driver can walk every layer type.
Solution
Automate the traversal:
7z l -slt <archive>lists contents. The firstPath =line is the archive itself; subsequentPath =lines are members.Type =gives the format.7z x -y -o<dir> <archive>extracts. If it fails and the type is7z, retry with-p<candidate>where the candidate is the first member's name (.name, the full filename with extension, is what works;.stemtried as fallback).- Filter out
@PaxHeader, then recurse into the single remaining extracted file.
The script traversed 1,250 archive layers. At level 1250, layer1000.zip extracted to flag.txt containing the flag.
python read-only • copy allowed#!/usr/bin/env python3 from pathlib import Path import re, shutil, subprocess root = Path(__file__).resolve().parent current = root / "challenge.zip" work = root / "layers" shutil.rmtree(work, ignore_errors=True) work.mkdir() for level in range(1, 10000): listing = subprocess.run(["7z", "l", "-slt", str(current)], capture_output=True, text=True) archive_type = re.search(r"^Type = (.+)$", listing.stdout, re.M) if listing.returncode or not archive_type: print(f"Stopped at level {level}: {current} is not an archive") print(current.read_bytes()[:1000]) break paths = re.findall(r"^Path = (.+)$", listing.stdout, re.M)[1:] # skip the archive itself out = work / f"{level:04d}" out.mkdir() command = ["7z", "x", "-y", f"-o{out}", str(current)] result = subprocess.run(command, capture_output=True, text=True) password = None if result.returncode and archive_type.group(1).lower() == "7z" and paths: for candidate in (Path(paths[0]).name, Path(paths[0]).stem): shutil.rmtree(out); out.mkdir() result = subprocess.run(command + [f"-p{candidate}"], capture_output=True, text=True) if result.returncode == 0: password = candidate break if result.returncode: raise RuntimeError(f"Extraction failed at {current}") files = [p for p in out.rglob("*") if p.is_file() and p.name != "@PaxHeader"] print(level, archive_type.group(1), current.name, "password=" + repr(password), "->", [p.name for p in files]) current = files[0]