SECCON 2017 Online CTF: Man-in-the-middle on SECP384R1
problem
A template of man-in-the-middle attack is given. Fill blanks.
solution
Attack to the ECDH.
The point is: use CBC mode for the AES, and the initialization vector is decided by comparing to the flag format SECCON{...}
.
note
konjo solves almost all. I’ve only found about CBC mode.
implementation
exploit.py
:
#!/usr/bin/python3
import mitm
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = "mitm.pwn.seccon.jp"
s.connect((host, 8000))
def fixed(msg):
data = s.recv(len(msg))
assert data
fixed(b"[dev0 to dev1]:")
data = s.recv(120)
payload = mitm.payload(data, 0) ## todo
s.send(payload)
fixed(b"\n[dev1 to dev0]: OK\n")
fixed(b"[dev1 to dev0]:")
data = s.recv(120)
payload = mitm.payload(data, 1) ## todo
s.send(payload)
fixed(b"\n[dev0 to dev1]: OK\n")
fixed(b"[KBKDF: SHA256, Encryption: AES]\n")
mitm.derive_keys() ## derive keys
fixed(b"[dev0 to dev1]:")
data = s.recv(256)
ct = mitm.mitm(data) ## todo
s.send(ct)
fixed(b"\n[dev1 to dev0]: OK\n")
fixed(b"[dev1 to dev0]:")
data = s.recv(256)
mitm.decrypt(data) ## todo
mitm.py
:
# Python Version: 3.x
# https://pypi.python.org/pypi/cryptography
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.asymmetric import ec
endian = 'big'
my_q = ec.generate_private_key(ec.SECP384R1(), default_backend())
peer_q = [ None, None ]
def payload(data, i):
# recv
header = data[: 24]
x = int.from_bytes(data[24 :][: 48], endian)
y = int.from_bytes(data[24 + 48 :], endian)
peer_q[i] = ec.EllipticCurvePublicNumbers(x, y, ec.SECP384R1()).public_key(default_backend())
# send
my_q_x = my_q.public_key().public_numbers().x
my_q_y = my_q.public_key().public_numbers().y
return header + my_q_x.to_bytes(48, endian) + my_q_y.to_bytes(48, endian)
def sha256(data):
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(data)
return digest.finalize()
shared_key = [ None, None ]
def derive_keys():
for i in range(2):
digest = sha256(my_q.exchange(ec.ECDH(), peer_q[i]))
shared_key[i] = Cipher(algorithms.AES(digest), modes.CBC(b'0000000000000000'), default_backend())
def run_crypto(cryptor, data):
buf = bytearray(4098)
len_crypted = cryptor.update_into(data, buf)
return bytes(buf[: len_crypted]) + cryptor.finalize()
def mitm(data):
data = run_crypto(shared_key[0].decryptor(), data)
print(data)
data = run_crypto(shared_key[1].encryptor(), data)
return data
def decrypt(data):
data = run_crypto(shared_key[1].decryptor(), data)
print(data)