After a long investigation we have revealed the enemy’s service, which provides their agents with any needed documents. Recent events indicate that there are double agents among us. We need to read the double_agents.txt file in order to identify their names and treat them accordingly. Can you do it?

Category: crypto

Solver: kh1

Flag: HTB{1v_sh01d_b3_r4nd0m}

Writeup

When connecting to the server, it sends

Welcome, agent! Request a document:

When sending something after this, the server interprets it as hexadecimal data and decodes it. If the decoded data is a multiple of 16 bytes long, it is decrypted (using AES in CBC mode) and the content of the file with the decrypted string as name is returned.

If no file with such name exists (or another exception occurs), the server returns the decrypted data (encoded in hexadecimal):

File not found: 68656c6c6f2e74787407070707070707

This can be used as a decryption oracle.

Since the decryption of the filename reuses the decryption key as initialization vector, the IV (and therefore the key) can be recovered by decrypting a ciphertext that consists of 32 null bytes and applying a bitwise xor operation to the first and second block p0 and p1 of the returned cleartext.

dec_cbc(d, key, iv) means AES CBC mode decryption of ciphertext d using key key and IV iv, dec(c, key) is AES decryption of the block c using the key key, xor is bitwise xor and | denotes concatenation:

dec_cbc(c0|c1, key, key) = (dec(c0, key) xor key)|(dec(c1, key) xor c0) = p0|p1

p0 = dec(c0, key) xor key
p1 = dec(c1, key) xor c0

let c = c0 = c1 = 0

p0 xor p1 = dec(c0, key) xor key xor dec(c1, key) xor c0    | c0 = c1 = c
          = dec(c, key) xor dec(c, key) xor key xor c
          = key

This yields the decryption key that can then be used to encrypt the desired filename.

Sending the padded, encrypted filename to the server results in a response containing the flag.

flag

Solver

import socket
import operator
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

ADDRESS = ("localhost", 23333) # changeme
BLOCKSIZE = 16

def send_and_receive(data):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(ADDRESS)
    sock.recv(4096)
    sock.sendall(data.hex().encode())
    result = sock.recv(4096).decode().strip()
    sock.close()
    return result

def decrypt(data):
    return bytes.fromhex(send_and_receive(data).split(" ")[3])

def extract_iv():
    text = decrypt(bytes(BLOCKSIZE * 2))
    iv = map(operator.xor, text[:BLOCKSIZE], text[BLOCKSIZE:])
    return bytes(iv)

def encrypt(data, key, iv):
    cipher = AES.new(key, AES.MODE_CBC, iv)
    return cipher.encrypt(pad(data, BLOCKSIZE))


if __name__ == "__main__":
    key = extract_iv()
    ciphertext = encrypt(b"double_agents.txt", key, key)
    print(send_and_receive(ciphertext))