# Cognitive Reminder Call — Writeup ## TL;DR The service “authenticates” messages with a CRC32‐based MAC: ``` tag = CRC32( key || nonce || message ) ``` CRC32 is linear. From any one valid `(nonce, message, tag)` you can recover the internal CRC state after processing the secret key (i.e., `CRC32(key)`), which lets you forge valid tags for any future `(nonce, message)`. The server also “remembers” the key only through four CRC32 values of its four parts. Because CRC32 is easy to collide, you can fabricate your own four byte strings whose individual CRC32s equal those four numbers. The server then derives the AES key from the parts you supplied and encrypts the reward with that key—so you can decrypt the reward locally. --- ## Challenge Flow (Observed) 1. Banner shows a ciphertext: `Here is the flag: ` followed by an authenticated note: `Note: ... tag is with nonce .` 2. The server prints 4 integers: `[c1, c2, c3, c4]`, claiming they’re “cognitive reminders” of the key (i.e., `CRC32(part_i)`). 3. It asks you to supply `Part 1..4 (hex)`, then demands: * `Please provide your nonce (hex):` * `Please provide the tag of the concatenation of the nonce and the 4 parts (hex):` 4. On success it prints: `Thanks for reminding me! Here is a reward: ` …along with an authenticated note. --- ## Vulnerability 1 — CRC32 as a MAC CRC32 is not a cryptographic MAC. Treated as a state machine over GF(2), the update ``` s’ = CRC32_update(s, data) ``` is affine in the 32-bit state `s`. The whole computation ``` tag = CRC32(message, CRC32(nonce, CRC32(key))) ``` is a composition of two affine maps applied to an unknown 32-bit vector `CRC32(key)`. ### Recovering `CRC32(key)` from one sample Let: * `N` = bytes of `nonce1` * `M` = bytes of `message1` * `T` = observed tag (32-bit) * `sK` = unknown `CRC32(key)` Define the affine operator for a byte string `X` as: ``` f_X(s) = A_X * s ⊕ b_X (over GF(2) on 32 bits) ``` `A_X, b_X` can be derived by calling the real CRC32 update on: * initial state 0 → gives `b_X` * each basis state `2^i` → columns of `A_X` Then: ``` T = f_M( f_N(sK) ) = (A_M A_N) sK ⊕ (A_M b_N ⊕ b_M) ``` The 32×32 matrix `A = A_M A_N` is invertible, so: ``` sK = A^{-1} ( T ⊕ (A_M b_N ⊕ b_M) ) ``` This gives `CRC32(key)` exactly as the implementation uses it (no guessing about polynomials, reflect, or endianness). > Practical note: some services MAC the line with or without the trailing newline. Test both variants against multiple “Note:” lines and choose the one that matches them all. --- ## Vulnerability 2 — “Cognitive reminders” are just CRC32s The server only verifies `CRC32(part_i) == c_i`. For CRC32 you can efficiently find preimages: * Fix a 4-byte prefix (e.g., `\x00\x00\x00\x00`), * Solve for a 4-byte suffix `S` so that `CRC32(prefix || S) = target`. Because the CRC update is affine in the initial state, this reduces to a 32×32 linear system over GF(2) (one per target). You get tiny 8-byte “parts” per reminder. The server derives the AES key as: ``` key = SHA256(part1 || part2 || part3 || part4) ``` and uses it to encrypt the reward. Since you chose the parts, you know the key. --- ## Exploit Plan (Step-by-Step) 1. Parse the transcript until “Part 1 (hex):”. Collect: * the four integers `[c1..c4]`, * at least one `(nonce, message, tag)` from a “Note:” line (usually the line right after “Here is the flag: …”). 2. Recover `sK = CRC32(key)` with the linear-algebra method above (try both “include newline” and “no newline”, pick the one that validates across notes). 3. Build four parts with `CRC32(part_i)=c_i`. A convenient construction is 8 bytes per part: `00000000 || suffix_i`. 4. Send the four parts when prompted. 5. Nonce + Tag: * Choose any fresh 4-byte nonce `n`. * The service asks for the tag of the *concatenation of the nonce and the 4 parts*. Two common shapes exist: * `payload = nonce || part1 || part2 || part3 || part4` * `payload = part1 || part2 || part3 || part4` (despite the wording) * Compute: ``` tag = CRC32(payload, CRC32(nonce, sK)) ``` Try the first shape; if rejected, try the second. 6. On success, the server prints the reward ciphertext (`IV || CT`). Derive `key = SHA256(part1||part2||part3||part4)` and AES-CBC decrypt to recover the final flag. --- ## Pseudocode Snippets ### Recover `CRC32(key)` from one sample ```python def crc_update(state, data): # uses binascii.crc32 return binascii.crc32(data, state) & 0xffffffff def build_affine(data): b = crc_update(0, data) cols = [] for i in range(32): cols.append(crc_update(1<