CTF-Writeups

Skyglyph II: Blind Drift - CTF Challenge

Category: Misc / Crypto / Forensics
Difficulty: Very Hard
Flag: 0xfun{w0w_Y0u_4R3_G0oD_4t_Th1s_ST4r_Th1N9}

📥 Download Challenge

Download Skyglyph II Blind Drift.zip - Try to solve it yourself before reading the writeup!


Challenge Summary

This edition is designed to reduce overwhelm by giving progress feedback: each frame decrypts one part of the flag independently.

Solve frame1 → decrypt cipher1.bin → you get PART 1/4. Repeat for frames 2–4, then concatenate the parts.


What You’re Given


Per-Frame Key Rule (The Oracle)

For one frame:

  1. Plate-solve: match detections ↔ catalog stars
  2. Keep matches where:
    • catalog mag < 6.0
    • detection sigma_px < 1.2
  3. Sort remaining matches by detection flux descending
  4. Take the first 64 star_id values

Encode the 64 IDs as little-endian u32 bytes and compute:

key = SHA256(bytes)

Decrypt the frame’s ciphertext with:

If any ID is wrong, AEAD authentication fails (perfect correctness check).


Step 1 — Project the Catalog to a 2D Tangent Plane (Gnomonic)

For frame1, use the seed center as (ra0, dec0). Convert degrees → radians and project:

Δra = wrap_to_[-π,π](ra - ra0)

cosc = sin(dec0)*sin(dec) + cos(dec0)*cos(dec)*cos(Δra)
u    = (cos(dec) * sin(Δra)) / cosc
v    = (cos(dec0)*sin(dec) - sin(dec0)*cos(dec)*cos(Δra)) / cosc

Step 2 — Bootstrap Pose (Similarity Transform)

Fit: [x,y] ≈ s * R(θ) * [u,v] + t

Bootstrap methods: triangle/quad hashing + RANSAC, or coarse roll/scale search + nearest-neighbor scoring (the seed reduces search space).

Filter detections first: keep sigma_px < ~2.0–2.2, take top 800–1500 by flux.

Step 3 — Refine Radial Distortion (k1, k2)

r² = u² + v²
f  = 1 + k1*r² + k2*r⁴
(u_d, v_d) = (u*f, v*f)

Iterate match → least squares → tighten gate.

Step 4 — Extract 64 IDs and Decrypt One Part

import hashlib, struct
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305

ids64 = [...]  # 64 ints for this frame
blob = b"".join(struct.pack("<I", x) for x in ids64)
key = hashlib.sha256(blob).digest()

nonce = open("nonce1.bin","rb").read()
ct    = open("cipher1.bin","rb").read()
aad   = b"PlateSolve++|frame=1"

pt = ChaCha20Poly1305(key).decrypt(nonce, ct, aad)
print(pt.decode())

Repeat for frames 2–4. Concatenate the returned PART i/4: payloads.

Final Flag

Concatenating all 4 decrypted parts yields:

0xfun{w0w_Y0u_4R3_G0oD_4t_Th1s_ST4r_Th1N9}


← Back to Blog