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