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()