$ cat writeup.md…
$ cat writeup.md…
broncoctf2026
Task: a chemist's secret message given as a list of (row, column[, k]) tuples. Solution: read each tuple as a periodic-table coordinate identifying an element symbol (third value picks one letter), concatenate, and lowercase to get the flag.
This text file is what happens when a chemist tries to send you a top secret message.
Hint: all letters in the flag should be lowercase.
We are given secret.txt, a single line of tuples plus the literal characters {, }, and _:
(4, 17), (2, 16), (2, 15), (4, 9), { , (3, 2, 1), (5, 3), _ , (2, 17), (3, 13, 1), (4, 5), (2, 16), (4, 17, 2), (2, 1, 2), (4, 4, 1), (2, 2, 2), _ , ... , (3, 16), (9, 6), (3, 15), (4, 17, 2), (2, 1, 2), (3, 16), (2, 2, 2), }
Goal: decode the message into the bronco{...} flag.
The title "Atomic Substitution Theory" and the word "chemist" point directly at the periodic table. "Substitution" means we replace each token with a letter.
Each tuple is a coordinate on the periodic table:
(row, col) → the element sitting at that period/group → take its full symbol.(row, col, k) → the k-th letter (1-indexed) of that element's symbol.{, }, _ pass through unchanged; _ acts as a word separator (space).The prefix confirms the scheme immediately:
| Token | Element | Letters |
|---|---|---|
(4,17) | Br | Br |
(2,16) | O | O |
(2,15) | N | N |
(4,9) | Co | Co |
→ BrONCo — the CTF's brand ("Bronco"). Then {, (3,2,1)=Mg→M, (5,3)=Y → {MY.
Three-element tuples slice a single letter of a two-letter symbol, e.g. (4,17,2)=Br→r,
(2,1,2)=Li→i. The detached actinide row is treated as period 9, so (9,6) is Uranium.
Reading everything out and lowercasing yields:
bronco{REDACTED...REDACTED}
A naive letter-for-letter read produces two plausible-but-wrong words:
(3,13,1)(4,4,1) = Al→A + Ti→T = "at", but the intended
English (matching the pun "an element of surprise") is "an".(9,6) (Uranium) is read as its two-letter
form "Ur": S + Ur + P + r + i + S + e = surprise. Reading Uranium as a
single U gives the misspelled "suprise".So don't trust the first plausible English guess — confirm against the pun's meaning: the message is literally "my favorite messages have an element of surprise."
Map every coordinate to an element symbol, apply the letter-index for 3-tuples, pass
through the literals, and lowercase. Fix the single ambiguous word at → an to match
the intended phrase.
#!/usr/bin/env python3 import re # (period, group) -> element symbol, covering every coordinate in secret.txt. PT = { (1, 1): "H", (2, 1): "Li", (2, 2): "Be", (2, 15): "N", (2, 16): "O", (2, 17): "F", (3, 2): "Mg", (3, 13): "Al", (3, 15): "P", (3, 16): "S", (3, 17): "Cl", (4, 4): "Ti", (4, 5): "V", (4, 9): "Co", (4, 13): "Ga", (4, 17): "Br", (5, 3): "Y", (9, 6): "Ur", # Uranium (actinide row), two-letter form so we get "surprise" } def decode(path="secret.txt"): tokens = re.findall(r"\([^)]*\)|[{}_]", open(path).read()) out = [] for tok in tokens: if tok in "{}_": out.append(tok); continue n = [int(x) for x in re.findall(r"-?\d+", tok)] if len(n) == 2: out.append(PT[(n[0], n[1])]) # full symbol elif len(n) == 3: out.append(PT[(n[0], n[1])][n[2] - 1]) # k-th letter return "".join(out) # Naive read gives proper English except one ambiguous word ("at" -> "an"). FIXUPS = {"at": "an"} def to_flag(raw): low = raw.lower() head, _, rest = low.partition("{") content, _, tail = rest.rpartition("}") words = [FIXUPS.get(w, w) for w in content.split("_")] return f"{head}{{{'_'.join(words)}}}{tail}" if __name__ == "__main__": raw = decode() print("raw :", raw) # BrONCo{REDACTED..._AT_..._SUrPriSe} print("naive:", raw.lower()) # ...have_atREDACTED flag = to_flag(raw) print("flag :", flag) assert flag == "bronco{REDACTED}"
Running it:
raw : BrONCo{REDACTEDTREDACTED}
naive: bronco{REDACTEDtREDACTED}
flag : bronco{REDACTED}
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar