HTB Hack The Boo Practice: yesnce

Challenge Information

AttributeDetails
EventHack The Boo Practice
CategoryCryptography
Challengeyesnce
DifficultyVery Easy

Summary

During the pranking labs, ghosts create a spooky encryption algorithm to encrypt devices and create ransomware. However, the student has befriended a human and wants to help decrypt their files. The encryption uses AES-CTR mode with critical vulnerabilities: predictable initial counter values and key reuse across messages, allowing recovery of the original plaintext through XOR operations.


Analysis

The challenge provides an encryption script that uses AES-CTR mode:

class AdvancedEncryption:
def __init__(self, block_size):
self.KEYS = self.generate_encryption_keys()
self.CTRs = [Counter.new(block_size, initial_value=i) for i in range(len(MSG))]
def generate_encryption_keys(self):
keys = [[b'\x00'] * 16] * len(MSG)
for i in range(len(keys)):
for j in range(16):
keys[i][j] = os.urandom(1)
return keys
def encrypt(self, i, msg):
key = b''.join(self.KEYS[i])
ctr = self.CTRs[i]
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
return cipher.encrypt(pad(msg.encode(), 16))

Critical Vulnerabilities:

  1. Predictable Counter Values: Each message uses a counter initialized to i (0, 1, 2, 3)
  2. Known Plaintext: The messages.txt file contains the first 3 messages, which are plaintexts
  3. Different Keys: Each message is encrypted with a different random key
  4. Unknown 4th Message: The flag message is unknown but has a predictable counter value

Challenge Data:

Messages:

[
'Hm, I have heard that AES-CTR is a secure encryption mode!',
'I think it is not possible to break it, right?',
'HTB{?????????????????????????????????????????????}', # Flag - unknown
'This is why I used it to encrypt my secret information above, hehe.',
]

Encrypted messages:

983641d252da35432cdd8aaa490b24bc5ac0583f5881adbe95c5b16d4309878a37c0d38d523f2b45390294e7ed7fe276a1ac966868a34e1284f6215389342b35
3394443645cf87dbaf9cd2506209809663818391442f37553047d1fde12df974b0a4922621ba0d5693be403dfb0d2f31
5ff5b1855a683504035184fbbd52e236a09ac86879ba10428de65d66d0065f412ed765fb2593aef817a6c59ed373ee8192ab659a30b06723ee9d363e00e2c7f7
81ad907568a7525696bf5e75c61258407fca36cd25dbe9c845f2cc95d555e9c1cbbb12b44ddb0a5f85e71859608aa68b271836560e3ecabde06ca9dddd35c9dd027436cf1facf536e9b7a51d5d09bbf5

Solution

Step 1: Recover the keystream for known plaintexts

For messages 0, 1, and 3, we know the plaintext. In CTR mode:

ciphertext = plaintext XOR keystream
keystream = ciphertext XOR plaintext

For each known message:

from binascii import unhexlify
# Known plaintexts
messages = [
'Hm, I have heard that AES-CTR is a secure encryption mode!',
'I think it is not possible to break it, right?',
'This is why I used it to encrypt my secret information above, hehe.',
]
# Ciphertexts (messages 0, 1, 3)
ciphertexts = [
unhexlify('983641d252da35432cdd8aaa490b24bc5ac0583f5881adbe95c5b16d4309878a37c0d38d523f2b45390294e7ed7fe276a1ac966868a34e1284f6215389342b35'),
unhexlify('3394443645cf87dbaf9cd2506209809663818391442f37553047d1fde12df974b0a4922621ba0d5693be403dfb0d2f31'),
unhexlify('81ad907568a7525696bf5e75c61258407fca36cd25dbe9c845f2cc95d555e9c1cbbb12b44ddb0a5f85e71859608aa68b271836560e3ecabde06ca9dddd35c9dd027436cf1facf536e9b7a51d5d09bbf5'),
]
# Recover keystreams
from Crypto.Util.Padding import pad
keystreams = []
for i, (msg, ct) in enumerate(zip(messages, [ciphertexts[0], ciphertexts[1], ciphertexts[3]])):
padded_msg = pad(msg.encode(), 16)
ks = bytes(a ^ b for a, b in zip(ct, padded_msg))
keystreams.append(ks)

Step 2: Analyze the keystream to find patterns

Since each message uses a different key but predictable counter initialization, we can look for patterns in how the keystreams start. Messages with similar counter values will have related keystrams.

Step 3: Decrypt message 2 (the flag)

The encrypted flag message is at ciphertext[2]:

encrypted_flag = unhexlify('5ff5b1855a683504035184fbbd52e236a09ac86879ba10428de65d66d0065f412ed765fb2593aef817a6c59ed373ee8192ab659a30b06723ee9d363e00e2c7f7')
# The flag format is known: HTB{...}
# We can use partial known plaintext attack or brute force the keystream
# by trying to identify which bytes correspond to the flag prefix
from Crypto.Util.Padding import unpad
# If we can derive the keystream for message 2, we can decrypt it:
# plaintext = ciphertext XOR keystream

Step 4: Use frequency analysis or known flag format

Flags always start with HTB{, which is 4 known bytes. We can XOR the first bytes of the ciphertext with the known plaintext to recover the first part of the keystream, then use that to decrypt the rest.


Key Takeaways

  • AES-CTR mode requires unique nonces/counters for each encryption operation with the same key
  • Reusing the same counter values with different keys is still insecure if any plaintexts are known
  • Known plaintext attacks on stream ciphers completely break security
  • Counter mode security depends critically on counter uniqueness and randomness
  • Always use authenticated encryption (GCM, ChaCha20-Poly1305) instead of bare CTR mode