NB: This WriteUp does not give you the answer to the CTF since the machine is not retired. But It will explain the vulnerability around ChaCha20 and the reuse of nounce which is relevant in the Challenge.
Challenge Overview
In the “Crypto — The Last Dance” challenge, we are provided with a zip folder containing two files: source.py
and out.txt
. Our goal is to decrypt the contents encrypted by the provided script and find the hidden flag.
Provided Files
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)
out.txt:
c4a66edfe80227b4fa24d431
7aa34395a258f5893e3db1822139b8c1f04cfab9d757b9b9cca57e1df33d093f07c7f06e06bb6293676f9060a838ea138b6bc9f20b08afeb73120506e2ce7b9b9dcd9e4a421584cfaba2481132dfbdf4216e98e3facec9ba199ca3a97641e9ca9782868d0222a1d7c0d3119b867edaf2e72e2a6f7d344df39a14edc39cb6f960944ddac2aaef324827c36cba67dcb76b22119b43881a3f1262752990
7d8273ceb459e4d4386df4e32e1aecc1aa7aaafda50cb982f6c62623cf6b29693d86b15457aa76ac7e2eef6cf814ae3a8d39c7
Analysis
Script functionality:
The provided source.py
script performs the following steps

Line 13–16: Defines the
encryptMessage
function to encrypt a message using ChaCha20 with a given key and nonce. 
Line 18–20: Defines the
writeData
function to write data to a file. 
Line 22–31: In the main block, a predefined message and a secret flag are encrypted using the same key and nonce. The IV, encrypted message, and encrypted flag are then written to
out.txt
.
Identifying the Vulnerability:
The main vulnerability in the provided script is the reuse of the nonce (IV) with the same key for encrypting two different plaintexts. This issue arises due to the nature of stream ciphers like ChaCha20.
How Stream Ciphers Work
Stream ciphers operate by generating a keystream, a sequence of pseudorandom bits, which is then XORed with the plaintext to produce the ciphertext. The encryption process can be summarized as follows:
 Keystream Generation: Using a secret key and a nonce (IV), the stream cipher generates a keystream.
 Encryption: The keystream is XORed with the plaintext to produce the ciphertext.
Importance of the Nounce
The nonce (Number used ONCE) is crucial in ensuring the uniqueness of the keystream for each encryption operation. When the same nonce and key are used to encrypt different plaintexts, the same keystream is generated. This reuse creates a critical vulnerability.
Vulnerability: Nounce Reuse
When a nonce is reused with the same key, the same keystream is applied to different plaintexts. This allows for a knownplaintext attack:
 KnownPlaintext Attack: If an attacker knows or can guess one of the plaintexts (knownplaintext), they can XOR the known plaintext with the corresponding ciphertext to recover the keystream: keystream = ciphertext ⊕ knownplaintext.
 Keystream Recovery: Once the keystream is recovered, the attacker can decrypt any other ciphertexts encrypted with the same keystream by XORing the keystream with the ciphertext: plaintext = ciphertext ⊕ keystream.
Exploiting the Vulnerability
In the provided script, the same nonce and key are used to encrypt both a predefined message and a secret flag. This can be exploited as follows:

Extract the Known Plaintext and Corresponding Ciphertext: We know the plaintext message and its corresponding ciphertext from
out.txt
.  Derive the Keystream: XOR the known plaintext with its ciphertext to derive the keystream.
 Decrypt the Flag: Use the derived keystream to XOR with the encrypted flag ciphertext, revealing the original flag.
By reusing the nonce, the script effectively leaks the keystream, compromising the security of both the encrypted message and the flag.
Let’s consider a simplified example to illustrate this:
 Plaintext1: “HELLO”
 Plaintext2: “WORLD”
 Nonce and Key: Both plaintexts are encrypted using the same nonce and key.
If we have: Keystream = ciphertext1 ⊕ plaintext1
We can then use the keystream to decrypt the second ciphertext: plaintext2 = ciphertext2 ⊕ keystream
This shows how critical nonce reuse can expose sensitive information encrypted with stream ciphers.
Exploitation Steps for the CTF:
 Extract the Known Plaintext and Corresponding Ciphertext (Line 14–18): We know the plaintext message: “Our counter agencies have intercepted your messages and a lot of your agent’s identities have been exposed. In a matter of days all of them will be captured”.
 Derive the Keystream (Line 25): XORing the known plaintext with its corresponding ciphertext allows us to derive the keystream used for encryption.
 Decrypt the Flag (Line 28): Using the derived keystream, we can XOR it with the encrypted flag to retrieve the original flag.
Decryption Script
Due to HackTheBox policies I’m not allowed to show the decryption script. But based on the information above you should be able to construct a Python script that will sole the Challenge. It will be posted when the Challenge is retired.
Output and Result
Running the decryption script gives us the decrypted flag:
Decrypted FLAG: HTB{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
IV: c4a66edfe80227b4fa24d431
Encrypted message: 7aa34395a258f5893e3db1822139b8c1f04cfab9d757b9b9cca57e1df33d093f07c7f06e06bb6293676f9060a838ea138b6bc9f20b08afeb73120506e2ce7b9b9dcd9e4a421584cfaba2481132dfbdf4216e98e3facec9ba199ca3a97641e9ca9782868d0222a1d7c0d3119b867edaf2e72e2a6f7d344df39a14edc39cb6f960944ddac2aaef324827c36cba67dcb76b22119b43881a3f1262752990
Encrypted flag: 7d8273ceb459e4d4386df4e32e1aecc1aa7aaafda50cb982f6c62623cf6b29693d86b15457aa76ac7e2eef6cf814ae3a8d39c7
Derived keystream: 35d631b5c13780e74a58c3a2405eddaf93259fcaf73fd8cfa985177387587b5c62b7840b629b1bfc121db00dcd4b9972ec0ebad26a66cbcb1232696996ee14fdbdb4f13f3035e5a8cecc3c3641ffd4904400ec8a8ea7acc939f4c2df13618baff2eca6e87a52cea4a5b73fbbcf10fa93c7434b1b09513fd3f572cda7fdcf8a40f521b6e2c589123c4fa6019a10b5db070273fe63eb7b4f6617074cf4
Decrypted flag bytes: 4854427b756e6433723537416e44316e395f35375233614d5f433150483352355f31355f35316d506c335f61355f374861377d
References:
https://en.wikipedia.org/wiki/ChaCha20Poly1305 — ChaCha20Poly1305
https://crypto.stackexchange.com/questions/32075/whathappensifanonceisreusedinchacha20poly1305 — What happens if a nonce is reused in ChaCha20Poly1305?
https://nvd.nist.gov/vuln/detail/CVE20191543 — CVE2019–1543 Detail
https://crypto.stackexchange.com/questions/66799/doeschacha20poly1305needrandomnonce — Does ChaCha20Poly1305 need random nonce?
https://eprint.iacr.org/2023/085.pdf — The Security of ChaCha20Poly1305 in the Multiuser Setting
Top comments (0)