Bunch of sec. enthusiasts who sometimes play CTF

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.


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 2501



Points: 100 Basepoints + 100 Bonuspoints * min(1, 3/93 Solves)

Category: crypto

Validations: 93


The server is sending 16 bytes of random data:

$ nc 2501

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 = ""
port = 2501
s.connect((host, port))
target = binascii.unhexlify(s.recv(1024)[:-1])

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:

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)


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
Sending to server...
Written on October 1, 2016