October 23, 2026
CREST CTF 2.0 — My Write-ups (5 Challenges, Rank 239)
# Author: Ayushi Aggarwal · CTF7: EdgeCrow50 · NullClass: ayushiaggarwal531514
By Ayushiaggarwal
3 min read
I competed solo in CREST CTF 2.0 (23 Oct 2026) as EdgeCrow50 and solved 5 challenges for 2100 points, finishing at rank 239. Below are my write-ups across OSINT, Steganography, a Network->Crypto->Web chain, and a full Boot2Root.
===================================================================
- GHOST DOMAIN — OSINT (200) =================================================================== Problem: A misconfigured DNS zone transfer (AXFR) for meridianlabs.ctf leaked, plus an internal changelog. The flag was split into fragments across two files: zone_dump.json and ops_changelog.json. Hint: "Not every record deserves your trust. Follow the right trail."
Approach:
- Most TXT records were decoys (MS=ms-fake…, v=spf1…). Only 5 TXT records had "frag=" AND a "serial_ref" field — those were genuine.
- Each serial_ref matched a change_serial in the changelog. Sorting the fragments by that serial (ascending) gave the correct order.
- Concatenating the ordered fragments produced a Base32 string (chars A-Z, 2–7, "=" padding). Decoding it gave the flag.
Tools: a JSON viewer to compare the files; CyberChef "From Base32" to decode. Flag: CREST{z0n3_tr4nsf3rs_ar3n7_supp0s3d_t0_b3_publ1c} [SCREENSHOT: Ghost Domain solve]
=================================================================== 2) AFTERIMAGE PROTOCOL — Steganography
Problem: a 1024x800 PNG. Recover the proof token; metadata and visible flag-shaped strings are NOT valid evidence.
Approach (4 layers):
- Layer 1: PNG tEXt metadata held CREST{metadata_is_not_evidence…} -> decoy.
- Layer 2: green-channel LSB held CREST{bitplanes_are_only_the_lobby…} -> decoy (and a hint: look deeper than the pixels).
- Layer 3: the PNG scanline FILTER bytes used only types 1–4 (no type 0) = 2 bits per row. Decoding 800 rows gave a zlib container (AP1!) with the protocol spec.
- Layer 4: the spec defined a pixel-selection predicate, a Morton-code ordering, a Fisher-Yates shuffle seeded by a BLAKE2s-derived PCG-XSH-RR PRNG, and a bit extracted as green-bit1. That reconstructed an AP3F sealed record decrypted with keyed BLAKE2s (verified MAC) -> the real flag.
Tools: Python + PIL (pixel access), zlib, hashlib (SHA-256/BLAKE2s), a hand-written PCG PRNG. The breakthrough was reading the PNG filter bytes — a channel beneath the pixels. Flag: CREST{f1lt3rs_b3f0r3_p1x3ls_9f42d8e1} [SCREENSHOT: Afterimage final decode]
=================================================================== 3) KEYMASTER — Network -> Crypto -> Web chain
Problem: three stages where each output is the next stage's key.
Stage 1 (Network): a pcap held HTTP NTLM auth. I decoded the Type-2 challenge and Type-3 response, then cracked NetNTLMv2 offline against the wordlist (HMAC-MD5 of the NTLMv2 hash). -> k.archivist : Meridian$55
Stage 2 (Crypto): the credential unlocked /attest, returning an ANOMALOUS curve where n == p. That breaks ECDLP via Smart's attack (p-adic lift + formal log), recovering the private key d directly from the public key. -> d = 0x75a058f10510d8318e535596f17c758707cbb291
Stage 3 (Web): d is the OAuth confidential-client secret. The redirect_uri check was a loose SUBSTRING match, so a callback that merely contained the registered URL leaked the admin code to my out-of-band endpoint. I exchanged it at /token with the secret for an admin token, then hit /admin/flag.
Tools: Python (base64/struct/pure-MD4/HMAC-MD5), a hand-written p-adic field for Smart's attack, curl + an OOB capture endpoint. Flag: ctf7{ntlmv2_smart_curve_oauth_redirect} [SCREENSHOT: Keymaster admin flag]
=================================================================== 4) NIGHTFALL — Boot2Root (two flags)
Problem: a Depot Portal web app (:8080) + SSH. Recover user.txt and root.txt.
Recon: robots.txt disallowed /dev-backup/, which exposed app.py.bak (source). Foothold: /internal/diag ran "ping … " + user input with shell=True; its blocklist stripped ; & ` but NOT the pipe |, giving RCE as www-data. Loot: /opt/depot/config.py held a DB password reused by the nightfall shell user. User: SSH'd to localhost as nightfall (the public :22 was the out-of-scope host, so I pivoted through the RCE). -> crest{r3us3d_cr3ds_0p3n_th3_d00r} Root: sudo -l allowed nf-maintenance (root, NOPASSWD); it eval'd $POST_HOOK from a maint-group-writable env file. I set the hook to read root.txt. -> crest{s0urc1ng_a_wr1t4bl3_3nv_as_r00t}
Tools: curl (RCE), Python + paramiko (SSH pivot), standard privesc enumeration (sudo -l, SUID, group hunt). Flags: crest{r3us3d_cr3ds_0p3n_th3_d00r} , crest{s0urc1ng_a_wr1t4bl3_3nv_as_r00t} [SCREENSHOT: Nightfall root + both flags]
=================================================================== 5) TRANSMISSION — Zero-width Steganography (5 layers)
Problem: an "archived chat log" whose formatting hides a message; preserve the exact file. The fake chat hinted at a "blank spaces trick."
Approach:
- The file had 485 invisible Unicode chars of four types (U+2060, U+034F, U+200B, U+200C) in STRICT ALTERNATION (data symbol, gap symbol, …). That structure is Morse, not binary: 034F=dot, 200B=dash, 2060=intra-letter gap, 200C=letter gap.
- Morse decoded to a Base32 string -> Base32 decode -> a Base64 string -> Base64 decode -> XOR ciphertext.
- Assuming the plaintext began "CREST{", a known-plaintext XOR revealed the key "KEY42". XORing with it gave the flag.
Tools: Python (codepoint + grapheme-cluster analysis; the combining mark U+034F was the clue it wasn't plain bits), a Morse table, base32/base64 decoders, and known-plaintext XOR. Flag: CREST{bl4nk_5p4ces_4re_d3lusi0ns} [SCREENSHOT: Transmission final XOR decode]
=================================================================== CLOSING
Final: 5 solved, 2100 points, rank 239 (solo). Favourite was the Keymaster chain for tying three disciplines together, and Transmission for hiding Morse inside zero-width characters. Thanks to the CREST CTF team for a great event.