HackTheBox 400Curves Challenge
Explore the basics of cybersecurity in the 400Curves Challenge on Hack The Box. This medium-level Challenge introduces encryption reversal and file handling concepts in a clear and accessible way, perfect for beginners.
https://app.hackthebox.com/challenges/368
Description
After the takeover of Felonious Forums, we’ve managed to identify and apprehend a low-level MonkeyBusiness APT operative. The developer was in charge of reselling components of the Zoid malware family. During a forensics investigation of the operative’s computer, we obtained the prototype source code of the TLS-based proxy service, which was used to obfuscate C2 traffic between the compromised machines to evade interception/detection. The remote host is still up, but the ssh keys we found have since been invalidated. During an assessment of the component’s source code, it looks like the key for the TLS-encrypted traffic is generated using the ECDH protocol with the P-256 curve, which is the most common curve on the Internet. Can you find a way to retrieve the proxy service’s private key?
Source
source.py
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from Crypto.Util.number import inverse, bytes_to_long
import socketserver
import signal
from secret import FLAG
a = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc
b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
E = {'a': a, 'b': b, 'p': p}
class Handler(socketserver.BaseRequestHandler):
def handle(self):
signal.alarm(0)
main(self.request)
class ReusableTCPServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
def sendMessage(s, msg):
s.send(msg.encode())
def recieveMessage(s, msg):
sendMessage(s, msg)
return s.recv(4096).decode().strip()
def add(P, Q, E):
if (P == (0, 0)):
return Q
elif (Q == (0, 0)):
return P
else:
Ea, Ep = E['a'], E['p']
x1, y1 = P
x2, y2 = Q
if ((x1 == x2) & (y1 == -y2)):
return ((0, 0))
else:
if (P != Q):
l = (y2 - y1) * inverse(x2 - x1, Ep)
else:
l = (3 * (x1**2) + Ea) * inverse(2 * y1, Ep)
x3 = ((l**2) - x1 - x2) % Ep
y3 = (l * (x1 - x3) - y1) % Ep
return x3, y3
def multiply(P, n, E):
Q = P
R = (0, 0)
while (n > 0):
if (n % 2 == 1):
R = add(R, Q, E)
Q = add(Q, Q, E)
n = n // 2
return R
def main(s):
sendMessage(s, "Establising the TLS handsake...\n")
while True:
C = recieveMessage(s, "Awaiting public key of the client...\n")
try:
x, y = [int(i) for i in C.strip().split()]
S = multiply((x, y), bytes_to_long(FLAG), E)
sendMessage(s, f"Shared secret: {S}\n")
except:
sendMessage(s, f"Error occured!\n")
if __name__ == '__main__':
socketserver.TCPServer.allow_reuse_address = True
server = ReusableTCPServer(("0.0.0.0", 1337), Handler)
server.serve_forever()
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
32
33
34
35
36
#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes
from pwn import log, remote, sys
from sage.all import crt, discrete_log, EllipticCurve, factor, GF
a = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc
p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
Fp = GF(p)
host, port = sys.argv[1].split(':')
def get_shared_secret(b):
E = EllipticCurve(Fp, [a, b])
G = E.gens()[0]
io = remote(host, int(port))
io.sendlineafter(b'Awaiting public key of the client...\n', f'{G[0]} {G[1]}'.encode())
io.recvuntil(b'Shared secret: ')
S = E(eval(io.recvline().decode()))
io.close()
return E.order(), G, factor(E.order()), S
def collect_dlogs(S, G, order, factors, skip, moduli, residues):
for p, e in factors[skip[0]:skip[1]]:
n = p ** e
t = order // n
moduli.append(n)
residues.append(discrete_log(t * S, t * G, operation='+'))
def main():
moduli, residues = [], []
for b, skip in [(0, (0, -2)), (1, (0, -1)), (4, (1, -3))]:
order, G, factors, S = get_shared_secret(b)
collect_dlogs(S, G, order, factors, skip, moduli, residues)
log.success(long_to_bytes(crt(residues, moduli)).decode())
if __name__ == '__main__':
main()
Summary
The 400Curves challenge on Hack The Box is a medium crypto challenge involving elliptic curve Diffie-Hellman and subgroup confinement. Participants submit crafted public keys on modified curves to leak partial information about the FLAG through scalar multiplications. By exploiting curves with smooth order and recovering discrete logs modulo small primes, they reconstruct the FLAG using the Chinese Remainder Theorem. The challenge highlights invalid curve attacks, demonstrating the risks of weak or unvalidated elliptic curve parameters in cryptographic protocols.