Real Smooth - author: overllama - crypto

WriteUp: ap10

Here is the server code provided in the prompt:

#!/usr/local/bin/python

from Crypto.Cipher import ChaCha20
from Crypto.Random import get_random_bytes
from secrets import FLAG

key = get_random_bytes(32)
nonce = get_random_bytes(8)

cipher = ChaCha20.new(key=key, nonce=nonce)
print(bytes.hex(cipher.encrypt(b'Slide to the left')))
print(bytes.hex(cipher.encrypt(b'Slide to the right')))

try:
    user_in = input().rstrip('\n')
    cipher = ChaCha20.new(key=key, nonce=nonce)
    decrypted = cipher.decrypt(bytes.fromhex(user_in))
    if decrypted == b'Criss cross, criss cross':
        print("Cha cha real smooth")
        print(FLAG)
    else:
        print("Those aren't the words!")
except Exception as e:
    print("Those aren't the words!")

We notice that the server reuses the same key and nonce for every encryption. To recover the keystream, we simply compute:

ks_block1 = c1 ⊕ b"Slide to the left"
ks_block2 = c2 ⊕ b"Slide to the right"
ks = ks_block1 ∥ ks_block2

Then we can encrypt the target plaintext b"Criss cross, criss cross" using that keystream. Here is a Python script to perform the exploit (the first two ciphertexts must be entered manually):

c1 = bytes.fromhex("06bd1ca0cad3e290b74cfe07550c7de8f9")
c2 = bytes.fromhex("8cc9674d669d1b4d2f25536efbf60bc61d93")

pt1 = b"Slide to the left"
pt2 = b"Slide to the right"

# Recover keystream
ks = bytearray()
ks.extend(x ^ y for x, y in zip(c1, pt1))
ks.extend(x ^ y for x, y in zip(c2, pt2))

# Encrypt target
target = b"Criss cross, criss cross"
ct_target = bytes(ks[i] ^ target[i] for i in range(len(target)))

print(ct_target.hex())

Using this approach against the challenge server:

$ nc smooth.chal.cyberjousting.com 1350
06bd1ca0cad3e290b74cfe07550c7de8f9
8cc9674d669d1b4d2f25536efbf60bc61d93
16a31cb7dcd3f58df84be54e55036ae7feac856d5b6cce1c
Cha cha real smooth
byuctf{ch4ch4_sl1d3?...n0,ch4ch4_b1tfl1p}