$ cat writeup.md…
$ cat writeup.md…
ASIS CTF 2026
Task: a permutation-group vault encodes each flag bit as a random word over {a,b}; intended path is generator key recovery. Solution: training samples self-describe the encoding (bit 1 iff word ends in b), so the verbatim ciphertext from menu 2 decodes bit by bit without group theory.
"Our lead cryptographer hackel proudly announced a 'revolutionary post-quantum vault' guarded by intricate algebraic group presentations. With a search space boasting over 1.6 quadrillion states, they confidently declared: 'No supercomputer on Earth could brute-force our permutations before the heat death of the universe!' Well... brute force is for amateurs. Armed with a fresh cup of coffee, a notebook, and a touch of modern group theory, can you reconstruct the secret representation, unlock the vault, and claim the flag?"
English summary: an interactive TCP service (nc 65.109.208.91 3771) presents a degree-11 permutation group "vault". The flag is encoded as a sequence of words over the two-letter alphabet {a, b}. The intended path is to recover an equivalent generator assignment (a "key") satisfying the published group presentation and submit it. In practice the ciphertext surface is printed verbatim and its encoding rule is fully disclosed by training samples, so the flag decodes directly.
The attachment contains the complete server source (Hackel/hackel.py, minus the flag module).
Group presentation (dressing). Two generator sets, uppercase A..E and lowercase a..e, are built identically over degree n = 11:
A = a 10-cycle conjugated by a random permutation, B = an 11-cycle conjugated by a random permutation, C = AB, D = A⁻¹BC, E = CD.A^10 = 1, B^11 = 1, AB = C, AD = BC, CD = E, ABD = E, AC = A²B, DE = DCD, CB = AB², ED = CDD (mirrored for lowercase).Aa = aA, Bb = bB, Ab = a·b·a⁹·A, Ba = b·a¹⁰·b·B.Actual encoding (the real story). In init_state() the flag is UTF-8 expanded to bits, and each bit becomes one random word over the lower alphabet:
0 → a^k with k ∈ [1..9] (only a characters);1 → a^k b with k ∈ [0..9] (exactly one b, always trailing).Menu surface.
a/b strings.The vulnerability. The "cipher" is a self-describing shape encoding with no secret: the word shape is the bit. Menu option 2 hands out the ciphertext verbatim, and the 16+16 training samples disambiguate the rule beyond doubt (zero-words never contain b; one-words always contain exactly one trailing b). The server even prints the word count — 496 words = 496 bits of UTF-8 flag — so nothing needs to be inferred. The intended group-theoretic key recovery (option 4) is bypassed entirely; option 5 is a trivial fallback using the same public rule.
Connect and dump option 2, then exit:
printf '2\n6\n' | nc 65.109.208.91 3771 > server_menu2.txt
Confirm the rule from the training samples, e.g. zero words look like a, aa, aaa, ... and one words look like aaab, aab, b, ... — exactly the two shapes from init_state().
Decode: a ciphertext word represents bit 1 iff it contains a b (equivalently, iff it ends in b); pack 8 bits per byte and decode UTF-8.
#!/usr/bin/env python3 """Decode Hackel flag ciphertext: bit = 1 iff the word contains 'b'.""" import re raw = open("server_menu2.txt", encoding="utf-8", errors="replace").read() # Grab the Encrypted Flag Words line (the comma-separated a/b strings) m = re.search(r"\[+\] Encrypted Flag Words \((\d+)\):\n\s*(.*?)\n", raw, re.S) count = int(m.group(1)) words = m.group(2).split(", ") assert len(words) == count bits = "".join("1" if "b" in w else "0" for w in words) out = bytearray() for i in range(0, len(bits) - 7, 8): out.append(int(bits[i:i + 8], 2)) print("[*] bits:", len(bits)) print("[*] decoded:", out.decode("utf-8", errors="replace"))
The 496 words decode cleanly into 62 printable UTF-8 bytes — the flag. (Fallback path, unused: option 5 emits 16 live words classifiable by the identical rule.)
Aa = aA, Bb = bB hint at a semidirect-product action, and the flag text confirms this was the designed theme) remains a nice exercise, but was strictly unnecessary here.$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar