TRANSLATOR

Decrypt a live encrypted radio broadcast from bunker XR-9 by exploiting a broken stream cipher nonce.

CryptographyStream CipherNonce ReuseBase64CRACKINMedium

Challenge Description

The Russians are broadcasting a message every night from bunker XR-9. We believe it contains coordinates for a new location for Gate experiments. We need to know what's in that broadcast before they open another door.

You have been given remote access to station XR-9's own encrypt terminal. The same terminal the Russians use. The same key. The same broken nonce.

We're given netcat access to a service that offers two options:

  1. Encrypt a plaintext (hex input)
  2. Intercept the broadcast (ciphertext)

Reconnaissance

Connecting to the service:

terminal
$ nc 65.1.39.118 4444

The terminal reveals:

Intercepting the Broadcast

terminal
> 2
[+] 4c53ab6e595be999bcaa7b0f1a15f1d3317930c44d5be0cae542cbbfa46f50b
c6c99a265144d581f48e88714146eff8ea4b6ce29e76ce2396927e4b2192b88c
002ae2fe3a9c126b257da88becbecb39c30d7fd8de0e70d0a5b8

Broadcast length: 88 bytes (176 hex characters)

Testing the Encrypt Oracle

terminal
> 1
[>] Enter plaintext (hex): 0x4142
[+] 5543

Observation: 0x4142 XOR 0x5543 = 0x1401

terminal
> 1
[>] Enter plaintext (hex): 0x4344
[+] 5745

0x4344 XOR 0x5745 = 0x1401same keystream bytes!

This confirms: the nonce never changes. Every encryption uses the same keystream.

Vulnerability: Stream Cipher Nonce Reuse

The service uses a stream cipher (AES-CTR or similar) where:

ciphertext = plaintext XOR keystream

Because the nonce is fixed ("broken nonce"), the keystream is identical for every encryption.

The Attack

If we encrypt a known plaintext P, we get:

C = P XOR K  (K = keystream)

Rearranging:

K = C XOR P

The simplest known plaintext is all-zero bytes, because:

0x00 XOR K = K

So encrypting null bytes directly reveals the raw keystream.

Exploitation

Step 1 — Extract the Keystream

Encrypt 47 null bytes (the service truncated at 47, so we sent what we could):

terminal
> 1
[>] Enter plaintext (hex): 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
[+] 14019254631dbbdced974a3e342ccbe9733c778d0361da8ea4168a82f55e1af0a3f8d416081c84f9adbfb00b187488

Keystream (47 bytes):

14019254631dbbdced974a3e342ccbe9733c778d0361da8ea4168a82f55e1af0a3f8d416081c84f9adbfb00b187488

Step 2 — XOR Keystream Against Broadcast

broadcast = bytes.fromhex(
    "4c53ab6e595be999bcaa7b0f1a15f1d3317930c44d5be0cae542cbbfa46f50b"
    "c6c99a265144d581f48e88714146eff8ea4b6ce29e76ce2396927e4b2192b88c"
    "002ae2fe3a9c126b257da88becbecb39c30d7fd8de0e70d0a5b8"
)

keystream = bytes.fromhex(
    "14019254631dbbdced974a3e342ccbe9733c778d0361da8ea4168a82f55e1af0"
    "a3f8d416081c84f9adbfb00b187488"
)

plaintext = bytes(a ^ b for a, b in zip(broadcast, keystream))
print(plaintext)

Step 3 — Decode the Output

XOR output (first 47 bytes of broadcast decrypted):

XR9::FREQ=11.9::BEGIN::DATA=Q1JLe1N0YXQxY18zY2gwMzVfaDFkZDNuX2IzbjM0dGhfbjAxczN9::END::X

The DATA= field is Base64 encoded!

Step 4 — Base64 Decode

import base64
print(base64.b64decode("Q1JLe1N0YXQxY18zY2gwMzVfaDFkZDNuX2IzbjM0dGhfbjAxczN9").decode())

Output:

CRK{Stat1c_3ch035_h1dd3n_b3n34th_n01s3}

Vulnerability Summary

Property Detail
Cipher typeStream cipher (fixed nonce)
AttackKnown-plaintext keystream recovery
Known plaintextAll-zero bytes → ciphertext = keystream
Secondary encodingBase64 inside the plaintext
Ops used3 of 10

Key Takeaways

  • Never reuse a nonce in stream ciphers. A single known-plaintext query recovers the entire keystream.
  • AES-CTR, ChaCha20, and RC4 all share this weakness under nonce reuse.
  • The "broken nonce" hint in the challenge description was the direct giveaway — always read the flavour text carefully.
  • Layered encodings (stream cipher → Base64) are common in CTF challenges; always check output for secondary encoding after decryption.

Flag

Decrypted from broadcast
CRK{Stat1c_3ch035_h1dd3n_b3n34th_n01s3}
WEAVE
Stacked Logs