Blunt Writeup  Cyber Apocalypse 2024
→ 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 DiffieHellman key exchange.
The description of the challenge is shown below.
→ 2 Key Techniques
The key techniques employed in this writeup are:
 Manual Python source code review
 Brute forcing a private key used in a DiffieHellman key exchange.
→ 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 DiffieHellman key agreement
protocols:

p
, the prime number of the underlying group 
g
, the base number 
A
, the public key for one party of the exchange 
B
, the public key for the other party of the exchange
p = 0xdd6cc28d
g = 0x83e21c05
A = 0xcfabb6dd
B = 0xc4a21ba9
ciphertext = b'\x94\x99\x01\xd1\xad\x95\xe0\x13\xb3\xacZj{\x97z\x1a(&\xe8\x01\xe4Y\x08\xc4\xbeN\xcd\xb2*\xe6{'
→ 4.2 source.py
source.py
contains Python code that appears to implement
a DiffieHellman key agreement protocol to compute private keys
a
and b
on lines 1718 and their corresponding
public keys A
and B
on line 20. A shared key
is created on lines 2526, 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 2836 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, p1)
print(f'g = 0x{g:x}')
a = random.randint(1, p1)
b = random.randint(1, p1)
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:
However, both a
and b
are bounded by the
range [1, p)
on lines 1718 and p
has a
maximum bit strength of 32 bits, which is very weak for DiffieHellman.
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:

Lines 812: contents of
output.txt
. 
Line 15: iterate through all integers in the range
[1, p)
 Line 1617: every 1 million iterations, print a status message.
 Line 19: calculate a candidate shared secret.
 Line 2129: use the candidate shared secret to decrypt the ciphertext.

Line 3032: if the decrypted text contains
HTB{
, print it out and break the loop.
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{\x97z\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