Skip to main content

HackTheBox Challenge The Last Dance (Crypto)

Edwin Tok | Shiro
Author
Edwin Tok | Shiro
「 ✦ OwO ✦ 」

Classic nonce reuse vulnerability in ChaCha20. The source code shows both the known message and the flag are encrypted with the same key and nonce.

$ cat source.py
from Crypto.Cipher import ChaCha20
from secret import FLAG
import os


def encryptMessage(message, key, nonce):
    cipher = ChaCha20.new(key=key, nonce=iv)
    ciphertext = cipher.encrypt(message)
    return ciphertext


def writeData(data):
    with open("out.txt", "w") as f:
        f.write(data)


if __name__ == "__main__":
    message = b"Our counter agencies have intercepted your messages and a lot "
    message += b"of your agent's identities have been exposed. In a matter of "
    message += b"days all of them will be captured"

    key, iv = os.urandom(32), os.urandom(12)

    encrypted_message = encryptMessage(message, key, iv)
    encrypted_flag = encryptMessage(FLAG, key, iv)

    data = iv.hex() + "\n" + encrypted_message.hex() + "\n" + encrypted_flag.hex()
    writeData(data)

$ cat out.txt
c4a66edfe80227b4fa24d431
7aa34395a258f5893e3db1822139b8c1f04cfab9d757b9b9cca57e1df33d093f07c7f06e06bb6293676f9060a838ea138b6bc9f20b08afeb73120506e2ce7b9b9dcd9e4a421584cfaba2481132dfbdf4216e98e3facec9ba199ca3a97641e9ca9782868d0222a1d7c0d3119b867edaf2e72e2a6f7d344df39a14edc39cb6f960944ddac2aaef324827c36cba67dcb76b22119b43881a3f1262752990
7d8273ceb459e4d4386df4e32e1aecc1aa7aaafda50cb982f6c62623cf6b29693d86b15457aa76ac7e2eef6cf814ae3a8d39c7%  

Stream ciphers like ChaCha20 work by XORing the plaintext with a keystream. If you reuse the same key + nonce, you get the same keystream. This means:

ciphertext1 = plaintext1 ⊕ keystream
ciphertext2 = plaintext2 ⊕ keystream

If we know plaintext1, we can recover the keystream:

keystream = ciphertext1 ⊕ plaintext1

Then use it to decrypt ciphertext2:

plaintext2 = ciphertext2 ⊕ keystream
$ cat solve.py
#!/usr/bin/python3

import binascii

# Known plaintext
known_message = (
    b"Our counter agencies have intercepted your messages and a lot "
    b"of your agent's identities have been exposed. In a matter of "
    b"days all of them will be captured"
)

# Read data from the provided output file
with open("out.txt", "r") as f:
    lines = f.read().splitlines()

# Extract nonce, encrypted message, and encrypted flag
nonce = binascii.unhexlify(lines[0])
encrypted_message = binascii.unhexlify(lines[1])
encrypted_flag = binascii.unhexlify(lines[2])

# Derive the keystream from the known plaintext
keystream = bytes(m ^ c for m, c in zip(known_message, encrypted_message))

# Decrypt the flag using the keystream
flag = bytes(c ^ k for c, k in zip(encrypted_flag, keystream))

print(f"FLAG: {flag.decode()}")

$ ./solve.py
FLAG: HTB{und3r57AnD1n9_57R3aM_C1PH3R5_15_51mPl3_a5_7Ha7}

Flag: HTB{und3r57AnD1n9_57R3aM_C1PH3R5_15_51mPl3_a5_7Ha7}

Related