Post

HackTheBox Challenge The Last Dance (Crypto)

Writeup for HackTheBox Challenge The Last Dance

HackTheBox Challenge The Last Dance (Crypto)

Challenge Synopsis

To be accepted into the upper class of the Berford Empire, you had to attend the annual Cha-Cha Ball at the High Court. Little did you know that among the many aristocrats invited, you would find a burned enemy spy. Your goal quickly became to capture him, which you succeeded in doing after putting something in his drink. Many hours passed in your agency’s interrogation room, and you eventually learned important information about the enemy agency’s secret communications. Can you use what you learned to decrypt the rest of the messages? (Source)

Enumeration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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%  

The vulnerability in this code stems from the reuse of the same nonce (iv) and key for encrypting both the message and the FLAG.

When a nonce is reused, it allows attackers to compute the XOR difference between the two plaintexts, revealing a relationship between the messages. Especially if two ciphertexts (C1 and C2) are produced with the same key and nonce: \(C1⊕C2=P1⊕P2\) Where:

  • P1 and P2 are the plaintexts,
  • C1 and C2 are the ciphertexts.

If one of the plaintexts (message) is known or partially known, the attacker can derive the other plaintext (FLAG).

Exploitation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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}

Code:

1
  keystream = bytes(m ^ c for m, c in zip(known_message, encrypted_message))

What is happening?

  • zip(known_message, encrypted_message) pairs each byte of the known_message with its corresponding byte in the encrypted_message.

  • m ^ c performs a bitwise XOR between each byte of the known plaintext (m) and its corresponding ciphertext byte (c).

  • The result of this XOR operation gives the keystream byte-by-byte: \(ciphertext=plaintext⊕keystream\)

  • Rearranging this equation: \(keystream=ciphertext⊕plaintext\)

Code:

1
  flag = bytes(c ^ k for c, k in zip(encrypted_flag, keystream))

What is happening?

  • zip(encrypted_flag, keystream) pairs each byte of the encrypted_flag with its corresponding byte of the derived keystream.

  • c ^ k performs a bitwise XOR between each byte of the ciphertext (encrypted_flag) and the corresponding byte of the keystream.

  • This recovers the original plaintext of the FLAG: \(plaintext=ciphertext⊕keystream\)

Flag: HTB{und3r57AnD1n9_57R3aM_C1PH3R5_15_51mPl3_a5_7Ha7}

This post is licensed under CC BY 4.0 by the author.