HackTheBox Write-up: Crypto – The Last Dance

RMAG news

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 = bOur counter agencies have intercepted your messages and a lot
message += bof your agents identities have been exposed. In a matter of
message += bdays 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 pseudo-random 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 known-plaintext attack:

Known-Plaintext Attack: If an attacker knows or can guess one of the plaintexts (known-plaintext), they can XOR the known plaintext with the corresponding ciphertext to recover the keystream: keystream = ciphertext ⊕ known-plaintext.

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

Here’s Python script to perform the decryption:

import binascii

# The known plaintext message
known_plaintext = (
bOur counter agencies have intercepted your messages and a lot
bof your agents identities have been exposed. In a matter of
bdays all of them will be captured
)

# Provided data from out.txt
iv_hex = c4a66edfe80227b4fa24d431
encrypted_message_hex = 7aa34395a258f5893e3db1822139b8c1f04cfab9d757b9b9cca57e1df33d093f07c7f06e06bb6293676f9060a838ea138b6bc9f20b08afeb73120506e2ce7b9b9dcd9e4a421584cfaba2481132dfbdf4216e98e3facec9ba199ca3a97641e9ca9782868d0222a1d7c0d3119b867edaf2e72e2a6f7d344df39a14edc39cb6f960944ddac2aaef324827c36cba67dcb76b22119b43881a3f1262752990
encrypted_flag_hex = 7d8273ceb459e4d4386df4e32e1aecc1aa7aaafda50cb982f6c62623cf6b29693d86b15457aa76ac7e2eef6cf814ae3a8d39c7

# Convert hex to bytes
iv = binascii.unhexlify(iv_hex)
encrypted_message = binascii.unhexlify(encrypted_message_hex)
encrypted_flag = binascii.unhexlify(encrypted_flag_hex)

# Derive the keystream from the known plaintext and its corresponding ciphertext
keystream = bytes([c ^ p for c, p in zip(encrypted_message, known_plaintext)])

# Decrypt the FLAG using the derived keystream
decrypted_flag = bytes([c ^ k for c, k in zip(encrypted_flag, keystream[:len(encrypted_flag)])])

# Print the decrypted FLAG in a readable format
try:
print(Decrypted FLAG:, decrypted_flag.decode(utf-8))
except UnicodeDecodeError:
print(Decrypted FLAG (raw bytes):, decrypted_flag)

# Debugging prints to track the decryption process
print(nIV:, iv.hex())
print(Encrypted message:, encrypted_message_hex)
print(Encrypted flag:, encrypted_flag_hex)
print(Derived keystream:, keystream.hex())
print(Decrypted flag bytes:, decrypted_flag.hex())

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/ChaCha20-Poly1305 — ChaCha20-Poly1305

https://crypto.stackexchange.com/questions/32075/what-happens-if-a-nonce-is-reused-in-chacha20-poly1305 — What happens if a nonce is reused in ChaCha20-Poly1305?

https://nvd.nist.gov/vuln/detail/CVE-2019-1543 — CVE-2019–1543 Detail

https://crypto.stackexchange.com/questions/66799/does-chacha20-poly1305-need-random-nonce — Does ChaCha20-Poly1305 need random nonce?

https://eprint.iacr.org/2023/085.pdf — The Security of ChaCha20-Poly1305 in the Multi-user Setting

Please follow and like us:
Pin Share