466 words
2 minutes
CryptoNite CTF 2026 - The Onion - Miscellaneous Writeup

Category: Miscellaneous Flag: TACHYON{1_w0nd3r_wh0s_b3hind_th3_m4sk_3hf84hfr}

Challenge Description#

This one you’ll just have to peel like an onion to get the flag lol.

Analysis#

The hint basically gave away the structure: this was going to be layered extraction, so I started by confirming the provided file type.

file "layer_100.zip"
layer_100.zip: Zip archive data, made by v2.0, extract using at least v2.0, last modified, last modified Sun, Feb 24 2026 19:35:50, uncompressed size 40960, method=deflate

From there I used a Python script to recursively unpack nested archives, because doing this manually 100 times is exactly how you lose your mind in a warmup. The script extracted each layer and looked for either a flag string or the next archive. The run log showed a clean peel pattern all the way down from layer_100.zip through layer_1.tar and layer_1.gz, then stopped when there were no archives left.

~/.venvs/py312-global/bin/python "/home/rei/Downloads/peel_onion.py"
[*] Layer 1: extracted layer_100.zip, archive candidates=1
...
[*] Layer 299: extracted layer_1.tar, archive candidates=1
[*] Layer 300: extracted layer_1.gz, archive candidates=0
[!] No further archives found. Stopping.

That output mattered because it confirmed the archive onion was fully peeled and the last layer had turned into plain payload data instead of another compressed file. At that point the solve was smooth and satisfying.

smile

I checked the deepest file directly and got a Base64-looking string.

cat "onion_work/layer_300/layer_1"
Q29uZ28gaGVyZSBpcyB5b3VyIGZsYWc6IFRBQ0hZT057MV93MG5kM3Jfd2gwc19iM2hpbmRfdGgzX200c2tfM2hmODRoZnJ9

Decoding that Base64 gave a sentence containing the final flag in the required TACHYON{...} format.

~/.venvs/py312-global/bin/python -c "import base64; s='Q29uZ28gaGVyZSBpcyB5b3VyIGZsYWc6IFRBQ0hZT057MV93MG5kM3Jfd2gwc19iM2hpbmRfdGgzX200c2tfM2hmODRoZnJ9'; print(base64.b64decode(s).decode())"
Congo here is your flag: TACHYON{1_w0nd3r_wh0s_b3hind_th3_m4sk_3hf84hfr}

Solution#

# /home/rei/Downloads/peel_onion.py
#!/~/.venvs/py312-global/bin/python
import re
import shutil
import subprocess
import tarfile
import zipfile
from pathlib import Path

FLAG_RE = re.compile(rb"TACHYON\{[^}\n\r]+\}")

def scan_for_flag(root: Path):
    for path in root.rglob("*"):
        if not path.is_file():
            continue
        try:
            data = path.read_bytes()
        except Exception:
            continue
        m = FLAG_RE.search(data)
        if m:
            return m.group(0).decode("utf-8", errors="ignore"), path
    return None, None

def is_archive(path: Path) -> bool:
    name = path.name.lower()
    archive_exts = (
        ".zip", ".tar", ".gz", ".bz2", ".xz", ".7z", ".rar", ".tgz", ".tbz", ".tbz2", ".txz",
    )
    if name.endswith(archive_exts):
        return True
    try:
        if zipfile.is_zipfile(path):
            return True
    except Exception:
        pass
    try:
        if tarfile.is_tarfile(path):
            return True
    except Exception:
        pass
    return False

def unpack_archive(archive: Path, out_dir: Path) -> bool:
    out_dir.mkdir(parents=True, exist_ok=True)
    try:
        shutil.unpack_archive(str(archive), str(out_dir))
        return True
    except Exception:
        pass
    try:
        cmd = ["7z", "x", "-y", f"-o{out_dir}", str(archive)]
        p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False)
        return p.returncode == 0
    except Exception:
        return False

def main():
    start_archive = Path("/home/rei/Downloads/layer_100.zip")
    base = Path("/home/rei/Downloads/onion_work")
    if base.exists():
        shutil.rmtree(base)
    base.mkdir(parents=True, exist_ok=True)

    current = start_archive
    seen = set()

    for i in range(1, 1000):
        layer_dir = base / f"layer_{i:03d}"
        ok = unpack_archive(current, layer_dir)
        if not ok:
            raise SystemExit(1)

        flag, loc = scan_for_flag(layer_dir)
        if flag:
            print(flag)
            return

        archives = [p for p in layer_dir.rglob("*") if p.is_file() and is_archive(p)]
        archives = [p for p in archives if str(p) not in seen]
        if not archives:
            return

        archives.sort(key=lambda p: (len(str(p)), str(p)))
        current = archives[0]
        seen.add(str(current))

if __name__ == "__main__":
    main()
~/.venvs/py312-global/bin/python "/home/rei/Downloads/peel_onion.py"
cat "onion_work/layer_300/layer_1"
~/.venvs/py312-global/bin/python -c "import base64; s='Q29uZ28gaGVyZSBpcyB5b3VyIGZsYWc6IFRBQ0hZT057MV93MG5kM3Jfd2gwc19iM2hpbmRfdGgzX200c2tfM2hmODRoZnJ9'; print(base64.b64decode(s).decode())"
Congo here is your flag: TACHYON{1_w0nd3r_wh0s_b3hind_th3_m4sk_3hf84hfr}
CryptoNite CTF 2026 - The Onion - Miscellaneous Writeup
https://blog.rei.my.id/posts/76/cryptonite-ctf-2026-the-onion-miscellaneous-writeup/
Author
Reidho Satria
Published at
2026-03-10
License
CC BY-NC-SA 4.0