Змеелов (Snake Catcher)
Змеелов (Snake Catcher)
Platform: hackerlab | Category: Misc | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-07-10 | Status: Solved Techniques: pyc_magic_recognition, serpent_encoding_decode, strings_flag_extraction, token_alphabet_indexing
Summary
Task: a Python file is one triple-quoted ASCII-art snake built from 168 tokens of form [PYTHON]{2}[0-9]. Solution: the title Змеелов points to Serpent encoding — each token maps to a byte via a 32-symbol letter-pair alphabet plus 32*digit, yielding a Python 2.7 .pyc whose string constant is the flag (readable with strings).
Змеелов (Snake Catcher) — hackerlab
Description
Предстоит проявить мастерство и стратегическое мышление, чтобы поймать хитрых пресмыкающихся, избегая их смертоносных укусов.
English summary: You must show skill and strategic thinking to catch the cunning
reptiles while avoiding their deadly bites. A ZIP archive snake_catcher.zip is
provided. It contains a single file flag (3281 bytes) — a Python script whose
entire content is one triple-quoted ASCII-art block depicting a snake/totem
wrapped around a pole. The goal is to recover the flag hidden in the art.
Analysis
The ASCII art is built from many 3-character tokens such as PY0, HN3, PO3,
TH3, YY7. Inspecting them reveals a strict structure:
- Each token has the form
[PYTHON]{2}[0-9]: two letters drawn only from the distinct letters of the word "PYTHON" (P, Y, T, H, O, N), followed by a single digit. - Only the digits
0, 1, 2, 3, 7appear. - There are exactly 168 such tokens in reading order.
Two letters from a 6-symbol set give up to 32 useful ordered pairs, and one digit
acts as a multiplier — this smells like a byte-per-token encoding: base + 32 * digit, which spans the full 0–255 byte range.
The semantic leap
The decisive clue is the wordplay in the title. «Змеелов» / "Snake Catcher" + tokens spelling PYTHON point directly to the Serpent encoding (a snake ≡ serpent). In misc challenges the title frequently names the exact technique.
Solution
Serpent encoding maps each token to one byte:
- The two-letter prefix indexes into a fixed 32-symbol alphabet of ordered letter pairs.
- The trailing digit is a high multiplier (× 32).
byte = ALPHABET.index(token[:2]) + 32 * int(token[2])
Alphabet (32 ordered letter pairs):
["PY","PT","PH","PO","PN","YP","YT","YH","YO","YN",
"TP","TY","TH","TO","TN","HP","HY","HT","HO","HN",
"OP","OY","OT","OH","ON","NP","NY","NT","NH","NO","PP","YY"]
Solver:
python read-only • copy allowed#!/usr/bin/env python3 import re from pathlib import Path ALPHABET = [ "PY","PT","PH","PO","PN","YP","YT","YH", "YO","YN","TP","TY","TH","TO","TN","HP", "HY","HT","HO","HN","OP","OY","OT","OH", "ON","NP","NY","NT","NH","NO","PP","YY", ] tokens = re.findall(r"[PYTHON]{2}[0-9]", Path("extracted/flag").read_text()) bytecode = bytes(ALPHABET.index(t[:2]) + 32 * int(t[2]) for t in tokens) Path("decoded.pyc").write_bytes(bytecode) # magic 03 f3 0d 0a -> Python 2.7 .pyc print(re.search(rb"CODEBY\{[^}]+\}", bytecode).group().decode()) # -> CODEBY{REDACTED}
Verification
Decoding all 168 tokens produces 168 bytes whose first 4 bytes are
03 f3 0d 0a — the Python 2.7 .pyc magic. This immediately validates the
decoding hypothesis: the byte stream is a compiled Python 2.7 module.
No full decompilation is needed. The flag is stored as a string constant in the
bytecode, so strings on the recovered .pyc reveals it directly (also showing
serpent and flag.py):
bash read-only • copy allowedstrings decoded.pyc # shows CODEBY{...}, serpent, flag.py