Noncense
Noncense
Platform: CUSTOM | Category: Crypto | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: ctr_nonce_reuse_attack, keystream_recovery, known_plaintext_attack
Summary
Walkthrough of the CUSTOM Crypto Noncense. Task: AES-CTR encryption with /dev/zero as random source producing zero nonce. Solution: Known plaintext attack using flag prefix to recover keystream, then XOR to decrypt.
Description
AES-CTR encryption task where get_random_bytes reads from /dev/zero instead of /dev/urandom.
Analysis
Vulnerability: The random number generator reads from /dev/zero, producing all-zero bytes. This means:
- Nonce = 0x00000000... (all zeros)
- Every encryption uses the same nonce
- Same keystream is generated for every block
AES-CTR Properties:
- CTR mode:
ciphertext = plaintext XOR keystream - Keystream depends only on key and nonce/counter
- With fixed nonce, keystream is deterministic
Attack: Known plaintext attack
- We know the flag starts with "spbctf{" (7 bytes)
- We can guess more: "spbctf{how_" (11 bytes) based on task theme
- XOR known plaintext with ciphertext to recover keystream
- Use keystream to decrypt remaining bytes
Solution
- Identify that nonce is all zeros (from /dev/zero)
- Use known flag prefix to recover keystream bytes
- Apply PKCS7 padding analysis to recover more keystream
- XOR ciphertext with recovered keystream
python read-only • copy allowed#!/usr/bin/env python3 def xor_bytes(a, b): """XOR two byte strings""" return bytes(x ^ y for x, y in zip(a, b)) # Ciphertext (hex) ciphertext = bytes.fromhex("...") # Known plaintext prefix (flag format) known_prefix = b"spbctf{how_" # 11 bytes known # Recover keystream from known plaintext keystream_partial = xor_bytes(ciphertext[:len(known_prefix)], known_prefix) print(f"Recovered keystream (first {len(known_prefix)} bytes): {keystream_partial.hex()}") # For CTR mode with zero nonce, keystream repeats every 16 bytes (AES block) # But counter increments, so we need full keystream # Method 1: If we have multiple ciphertexts with same keystream # ct1 XOR ct2 = pt1 XOR pt2 (keystream cancels) # Method 2: PKCS7 padding analysis # Last byte of plaintext is padding value (0x01 to 0x10) # Try each padding value to extend keystream recovery def try_padding(ciphertext, keystream_known): """Try to recover more keystream using PKCS7 padding""" block_size = 16 ct_len = len(ciphertext) # Try padding values 1-16 for pad_len in range(1, block_size + 1): # Padding bytes are all equal to pad_len padding = bytes([pad_len] * pad_len) # Position of padding in ciphertext pad_start = ct_len - pad_len # Recover keystream at padding position ks_at_pad = xor_bytes(ciphertext[pad_start:], padding) # Verify consistency with known keystream # (keystream repeats with counter, need to check alignment) print(f"Padding {pad_len}: keystream = {ks_at_pad.hex()}") # Full solution with complete keystream recovery def solve(ciphertext, known_prefix): # Recover keystream progressively keystream = bytearray(len(ciphertext)) # From known prefix for i, (c, p) in enumerate(zip(ciphertext, known_prefix)): keystream[i] = c ^ p # Extend using CTR counter structure and padding # ... (task-specific logic) # Decrypt plaintext = xor_bytes(ciphertext, bytes(keystream)) return plaintext # Example with full keystream (if recovered) # keystream = bytes.fromhex("...") # plaintext = xor_bytes(ciphertext, keystream) # print(plaintext.decode())
Key Takeaways / Lessons
- Mastering ctr_nonce_reuse_attack, keystream_recovery, known_plaintext_attack was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-03-09 • CUSTOM • Noncense • Difficulty: Medium