Diffy-Hiffy
Задание
Связь рейдеров перехвачена — они используют какой-то протокол для обмена ключами в реальном времени. Подключись к их каналу, разберись в механике обмена и найди способ расшифровать сообщение. Это последний шанс узнать их планы и защитить убежище!
Решение
Подключение для выполнения данного задания происходит по Netcat.
Для выполнения задания был дан файл с исходным кодом сервиса:
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
from threading import Thread
import hashlib
import random
import socket
import time
import os
p = 21939064273961432692869235542893309434365296719508334253822045076157821650630047212279097400258209801635218687194014309421774539657826955718443612198193637894945215120269429964294507963612184795033175591635352291302967656747476846944244765600058091168639460934760860868326266514083637259374687840565001460873188850800163176147059578426285991042291544199555653768310302902708201893293049574742797280456632089035058341779138857865427898515383676243788195320008247816405690477742229224304949780665765133150285293936629056854719648258757922775143286326810180018785995447730996681389941725954432835063148737663764568414527
g = 2
def diffie_hellman_generate_params():
a = random.randint(2, p-2)
A = pow(g, a, p)
return g, p, a, A
def encrypt_flag(flag, key):
cipher = AES.new(key, AES.MODE_CBC)
ct_bytes = cipher.encrypt(pad(flag.encode(), AES.block_size))
return cipher.iv + ct_bytes
def handle_client(conn, addr):
print(f"New connection from {addr}")
flag = f"ptech2024{{{os.getenv('flag')}}}\n"
try:
g, p, a, A = diffie_hellman_generate_params()
conn.sendall(f"g param: {g}\n".encode())
time.sleep(0.25)
conn.sendall(f"p param: {p}\n".encode())
time.sleep(0.25)
conn.sendall(f"A result: {A}\n".encode())
time.sleep(0.25)
B = int(conn.recv(4096).decode().strip())
time.sleep(0.25)
k = pow(B, a, p)
k_bytes = k.to_bytes((k.bit_length() + 7) // 8, byteorder='big')
k_hash = hashlib.sha256(k_bytes).digest()
encrypted_flag = encrypt_flag(flag, k_hash)
conn.sendall("Your flag:\n".encode())
time.sleep(0.25)
conn.sendall(encrypted_flag)
print(f"Encrypted flag sent to client: {addr}")
except Exception as e:
print(f"Error in session with {addr}: {e}")
finally:
conn.close()
def server_get_connections():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 12345))
server_socket.listen(10)
print(f"Server started. Listening port 12345....")
while True:
conn, addr = server_socket.accept()
client_thread = Thread(target=handle_client, args=(conn, addr))
client_thread.start()
if __name__ == "__main__":
server_get_connections()
В данном сервисе использован алгоритм Диффи — Хеллмана для получения ключа шифрования, а также шифр AES в режиме CBC (прочитать про режимы работы блочных шифров можно здесь) для шифрования флага.
Проведем анализ работы сервиса:
- Сначала используются параметры p и g, заданные в коде, и случайно сгенерированный параметр a для рассчета значения параметра A
- Далее эти параметры отправляются пользователю, и сервер ждет от пользователя параметр B
- Сервер вычисляет ключ
- Ключ переводится в байты, для которых берется хеш сумма
- Далее полученная хеш сумма используется для шифрования флага с помощью AES в режиме CBC
- Зашифрованный флаг отправляется пользователю
Для расшифровки флага необходимо вычислить ключ шифрования. Сервер программы отправляет пользователю такие данные:
g param: 2
p param: 21939064273961432692869235542893309434365296719508334253822045076157821650630047212279097400258209801635218687194014309421774539657826955718443612198193637894945215120269429964294507963612184795033175591635352291302967656747476846944244765600058091168639460934760860868326266514083637259374687840565001460873188850800163176147059578426285991042291544199555653768310302902708201893293049574742797280456632089035058341779138857865427898515383676243788195320008247816405690477742229224304949780665765133150285293936629056854719648258757922775143286326810180018785995447730996681389941725954432835063148737663764568414527
A result: 7836609260571610419397475408625198751625567856538952365788036450749229368559371198572323332899973755163519698867187857012805824993013911555984809799274301910115190772859503256139814116179006505411114917804792036479050884659650741500669206150402449587082295630080773890042276112694773040131308694111851530333542986809711943857130629658681388097431021356103305377220505853993031463508772348531695506926987989480090387484156436243581825774904798979950787528339768560663760643791694659481726287478209434998746179860633528639452805757005364917850985206813012984244780910595676358976265949919975467908194243553049629705026
Для рассчета параметра B, который сервер ожидает от пользователя, необходимо сгненерировать случайное число b и возвести полученное значение g в степень b по модулю p. Для рассчета общего ключа необходимо возвести B в степень a по модулю p. Далее необходимо перевести полученный ключ в байты и вычислить хеш аналогично тому, как это сделано в коде сервера. Преобразованный ключ можно использовать для расшифровки флага.
Ниже приведен код для решения данного задания:
from Crypto.Util.Padding import unpad
from Crypto.Cipher import AES
import hashlib
import socket
import random
import time
def diffie_hellman_generate_params(g, p):
a = random.randint(2, p-2)
A = pow(g, a, p)
return a, A
def compute_shared_key(B, a, p):
k = pow(B, a, p)
k_bytes = k.to_bytes((k.bit_length() + 7) // 8, byteorder='big')
return hashlib.sha256(k_bytes).digest()
def client_program():
server_address = ('localhost', 12345)
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(server_address)
print("Connected to server.")
try:
g = int(client_socket.recv(4096).decode().strip().split(": ")[1])
time.sleep(0.25)
p = int(client_socket.recv(4096).decode().strip().split(": ")[1])
time.sleep(0.25)
A = int(client_socket.recv(4096).decode().strip().split(": ")[1])
time.sleep(0.25)
a, A_client = diffie_hellman_generate_params(g, p)
print(f"Client's A: {A_client}")
B = A_client
client_socket.sendall(f"{B}\n".encode())
time.sleep(0.25)
some_text = client_socket.recv(4096)
time.sleep(0.25)
encrypted_flag = client_socket.recv(4096)
print(f"Encrypted flag: {encrypted_flag}")
k = compute_shared_key(A, a, p)
print(f"Calculated key: {k.hex()}")
iv = encrypted_flag[:16]
ct_bytes = encrypted_flag[16:]
cipher = AES.new(k, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(ct_bytes), AES.block_size)
print(f"Decrypted flag: {decrypted.decode('utf-8')}")
except:
print("error")
finally:
client_socket.close()
if __name__ == "__main__":
client_program()