$ cat writeup.md…
$ cat writeup.md…
dawgctf
Task: a PDF shows a 21-symbol ciphertext made of pigpen-like glyphs with 0, 1, or 2 dots. Solution: extract and deduplicate the glyph masks, notice standard pigpen fails, then use the Donald Duck hint to switch to the Dada Urka alphabet and decode the text as REDACTED.
The provided PDF contains a row of unusual symbols and explicitly references Donald and Scrooge.
Files provided:
dawgCTF_2026_vault_breaker.pdfSource: https://github.com/UMBCCyberDawgs/dawgctf-sp26/tree/main/Vault%20Breaker
English summary: the challenge gives a PDF containing 21 unfamiliar glyphs that resemble a pigpen-family symbol cipher. The goal is to identify the correct variant, decode the hidden password, and wrap it in the DawgCTF flag format.
The PDF did not contain a useful text layer, so the first step was to render or extract the page content as images and inspect the symbols directly. The page contained 21 unusual symbols arranged as a ciphertext.
Visually, the glyphs looked very close to pigpen / Masonic cipher symbols: angular pen shapes, some with no dots, some with one dot, and some with two dots.
The obvious first attempt was to treat the glyphs as ordinary pigpen symbols. That produced:
JNQVJBJ&W&TKGDCHHETVA
This is not remotely flag-like, not readable English, and not a plausible password. That failure was useful: it suggested the challenge was using a pigpen-family variant, not the standard alphabet.
Instead of relying only on eyeballing, I extracted the raw symbol masks from the PDF's image/XObject data and compared the resulting SMask streams. That made it possible to verify whether symbols that merely looked similar were actually identical.
This confirmed several exact duplicates:
After deduplicating all 21 positions, there were only 15 unique symbols. Labeling each new symbol in order gives the repetition pattern:
ABCDAEAFGFHIJKLMMNHDO
This is a very strong substitution-cipher fingerprint. Any correct plaintext must have the exact same equality pattern.
The challenge text explicitly referenced Donald and Scrooge. That is not random flavor: it points directly to the Donald Duck / Junior Woodchucks cipher known as Dada Urka.
Dada Urka belongs to the same general family as pigpen-style symbol ciphers, so it is easy to mistake for standard pigpen at first glance. Crucially, it also uses very similar pen shapes combined with 0, 1, or 2 dots.
Once that connection is recognized, the earlier nonsense decode becomes the clue that we were using the wrong alphabet table.
Using the Dada Urka alphabet, the 21 symbols decode cleanly to:
REDACTED
The positional mapping is:
1 E 2 X 3 T 4 R 5 E 6 M 7 E 8 L 9 Y 10 L 11 O 12 N 13 G 14 P 15 A 16 S 17 S 18 W 19 O 20 R 21 D
This also perfectly matches the earlier repetition structure:
symbol pattern: ABCDAEAFGFHIJKLMMNHDO plaintext: REDACTED
Examples:
That exact structural match is strong confirmation that the decode is correct.
dawgCTF_2026_vault_breaker.pdf.JNQVJBJ&W&TKGDCHHETVA.ABCDAEAFGFHIJKLMMNHDO.REDACTED.Full working helper script for the extracted sym_*_raw.png files:
#!/usr/bin/env python3 from pathlib import Path import hashlib ROOT = Path("tasks/metactf/vault_breaker") # Recovered from the Dada Urka alphabet after recognizing the Donald/Scrooge hint. DADA_URKA_DECODE = { "A": "E", "B": "X", "C": "T", "D": "R", "E": "M", "F": "L", "G": "Y", "H": "O", "I": "N", "J": "G", "K": "P", "L": "A", "M": "S", "N": "W", "O": "D", } def sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() def main(): raw_symbols = [ROOT / f"sym_{i:02d}_raw.png" for i in range(1, 22)] seen = {} labels = [] next_label_ord = ord("A") for path in raw_symbols: digest = sha256(path) if digest not in seen: seen[digest] = chr(next_label_ord) next_label_ord += 1 labels.append(seen[digest]) pattern = "".join(labels) plaintext = "".join(DADA_URKA_DECODE[label] for label in labels) flag = f"DawgCTF{{{plaintext.lower()}}}" print(f"unique symbols: {len(set(labels))}") print(f"pattern: {pattern}") print(f"plaintext: {plaintext}") print(f"flag: {flag}") if __name__ == "__main__": main()
Expected output:
unique symbols: 15 pattern: ABCDAEAFGFHIJKLMMNHDO plaintext: REDACTED flag: DawgCTF{REDACTED}
ABCDAEAFGFHIJKLMMNHDO already constrains the plaintext heavily.$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar