TUM CTF 2016 - haggis
The challenge is to create a plaintext with a given prefix that will result in a AES CBC-MAC mode using PKCS#7 padding.
Description
OMG, I forgot to pierce my haggis’ sheep stomach before cooking it, so it exploded all over my kitchen. Please help me clean up!
(Fun fact: Haggis hurling is a thing.)
nc 104.198.243.170 2501
Details
Points: 100 Basepoints + 100 Bonuspoints * min(1, 3/93 Solves)
Category: crypto
Validations: 93
Solution
The server is sending 16 bytes of random data:
$ nc 104.198.243.170 2501
6c762738c13149013a6ca5b30e8935df
According to the script, we had to respond by a message starting by ‘I solemnly swear that I am up to no good.\0’ and when authenticated with AES CBC-MAC using PKCS#7 padding the last ciphertext block matches these 16 bytes. Hopefully the key is known and it is all zero. So we can decrypt the given random byte with the zero key. We xored it with the previous ciphertext block and it will gave us the last plaintext block. The only problem is that the last byte of the block of the plaintext must finished by the byte 0x01 to have a correct padding. Thus I decided to bruteforce over the last byte of the previous plaintext byte until I got 0x01 at the end of the plaintext. Here is the complete solution:
#!/usr/bin/env python3
import os, binascii, struct
import socket
from Crypto.Cipher import AES
from Crypto.Util import strxor
pad = lambda m: m + bytes([16 - len(m) % 16] * (16 - len(m) % 16))
def encrypt(m, length):
crypt0r = AES.new(bytes(0x10), AES.MODE_CBC, bytes(0x10))
cipher = crypt0r.encrypt(length.to_bytes(0x10, 'big') + m)
return cipher[-0x10:]
def haggis(m):
crypt0r = AES.new(bytes(0x10), AES.MODE_CBC, bytes(0x10))
cipher = crypt0r.encrypt(len(m).to_bytes(0x10, 'big') + pad(m))
return cipher[-0x10:]
s = socket.socket()
host = "104.198.243.170"
port = 2501
s.connect((host, port))
target = binascii.unhexlify(s.recv(1024)[:-1])
print(binascii.hexlify(target).decode())
crypt1r = AES.new(bytes(0x10), AES.MODE_ECB)
tail = crypt1r.decrypt(target)
msg = b'I solemnly swear that I am up to no good.\0.....'
for i in range(256):
b = bytes([i])
block = encrypt(msg+b, len(msg+tail))
if strxor.strxor(tail, block)[15] == 1:
break
tail= strxor.strxor(tail, block)
msg = msg + b + tail[:-1]
if msg.startswith(b'I solemnly swear that I am up to no good.\0') and haggis(msg) == target:
print("Sending to server...")
s.send(binascii.hexlify(msg) + b"\n")
target = s.recv(1024)
print(target)
s.close
It was not working every time since for some inputs one byte bruteforce is not sufficient. However, after few tried I was able to get the flag:
$ ./solution.py
2ccf9b7c2bc5617c26f2110975f9008e
Sending to server...
b'hxp{PLz_us3_7h3_Ri9h7_PRiM1TiV3z}\n'
Done