forbytten blogs

Blunt Writeup - Cyber Apocalypse 2024

Last update:

1 Introduction

This writeup covers the Blunt Crypto challenge from the Hack The Box Cyber Apocalypse 2024 CTF, which was rated as having an ‘easy’ difficulty. The challenge involved brute forcing a private key used in a Diffie-Hellman key exchange.

The description of the challenge is shown below.

Blunt challenge description

2 Key Techniques

The key techniques employed in this writeup are:

3 Artifacts Summary

The downloaded artifact had the following hash:

$ shasum -a256 crypto_blunt.zip
ff554950fd03d7f739042c113e2622b6138c9c3489b5b71eb51e3cb7619244ba  crypto_blunt.zip

The zip file contained a single Python source code file, source.py, and output.txt:

$ unzip crypto_blunt.zip
Archive:  crypto_blunt.zip
   creating: challenge/
  inflating: challenge/source.py
  inflating: challenge/output.txt

$ shasum -a256 challenge/*
88cfc1b6ad50a9df20efdf8b4dae459088ba8719d4ca1225cc93fb32165ee79f  challenge/output.txt
5e027a2850eb0e0d2e644636538063d65bf4080cadbae3369855f0ed07683a1d  challenge/source.py

4 Static Analysis

4.1 output.txt

output.txt contains ciphertext and 4 variables which look suspiciously like components used in Diffie-Hellman key agreement protocols:

p = 0xdd6cc28d
g = 0x83e21c05
A = 0xcfabb6dd
B = 0xc4a21ba9
ciphertext = b'\x94\x99\x01\xd1\xad\x95\xe0\x13\xb3\xacZj{\x97|z\x1a(&\xe8\x01\xe4Y\x08\xc4\xbeN\xcd\xb2*\xe6{'

4.2 source.py

source.py contains Python code that appears to implement a Diffie-Hellman key agreement protocol to compute private keys a and b on lines 17-18 and their corresponding public keys A and B on line 20. A shared key is created on lines 25-26, which can be independently computed by either party of the exchange using a party’s secret key and the other party’s public key. Lines 28-36 use the key to encrypt the flag using the AES encryption algorithm.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.number import getPrime, long_to_bytes
from hashlib import sha256

from secret import FLAG

import random


p = getPrime(32)
print(f'p = 0x{p:x}')

g = random.randint(1, p-1)
print(f'g = 0x{g:x}')

a = random.randint(1, p-1)
b = random.randint(1, p-1)

A, B = pow(g, a, p), pow(g, b, p)

print(f'A = 0x{A:x}')
print(f'B = 0x{B:x}')

C = pow(A, b, p)
assert C == pow(B, a, p)

# now use it as shared secret
hash = sha256()
hash.update(long_to_bytes(C))

key = hash.digest()[:16]
iv = b'\xc1V2\xe7\xed\xc7@8\xf9\\\xef\x80\xd7\x80L*'
cipher = AES.new(key, AES.MODE_CBC, iv)

encrypted = cipher.encrypt(pad(FLAG, 16))
print(f'ciphertext = {encrypted}')

5 Vulnerability analysis

In the calculation of the shared key, only b and a are unknown:

C = pow(A, b, p)
assert C == pow(B, a, p)

However, both a and b are bounded by the range [1, p) on lines 17-18 and p has a maximum bit strength of 32 bits, which is very weak for Diffie-Hellman. Therefore, the value of a or b can be brute forced until the value generates a C that can decrypt the ciphertext. Additionally, given all flags for the challenges start with HTB{, a distinct terminating condition exists.

6 Implementing the decryptor

decrypt.py was implemented as below:

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.number import getPrime, long_to_bytes
from hashlib import sha256

import random

p = 0xdd6cc28d
g = 0x83e21c05
A = 0xcfabb6dd
B = 0xc4a21ba9
ciphertext = b'\x94\x99\x01\xd1\xad\x95\xe0\x13\xb3\xacZj{\x97|z\x1a(&\xe8\x01\xe4Y\x08\xc4\xbeN\xcd\xb2*\xe6{'

print(f"p: {p}")
for b in range(1, p):
    if b % 1000000 == 0:
        print(f"b: {b}")

    C = pow(A, b, p)

    # now use it as shared secret
    hash = sha256()
    hash.update(long_to_bytes(C))

    key = hash.digest()[:16]
    iv = b'\xc1V2\xe7\xed\xc7@8\xf9\\\xef\x80\xd7\x80L*'
    cipher = AES.new(key, AES.MODE_CBC, iv)

    decrypted = cipher.decrypt(ciphertext)
    if decrypted.find(b"HTB{") > -1:
        print(f'decrypted = {decrypted}')
        break

7 Obtaining the flag

The script was run and the flag was obtained:

$ python3 -mvenv venv
$ . ./venv/bin/activate
$ pip install pycryptodome
$ python3 decrypt.py
p: 3714892429
b: 1000000
b: 2000000
b: 3000000
b: 4000000
b: 5000000
b: 6000000
b: 7000000
b: 8000000
decrypted = b'HTB{y0u_n3ed_a_b1gGeR_w3ap0n!!}\x01'

8 Conclusion

The flag was submitted and the challenge was marked as pwned

Submission of the flag marked the challenge as pwned