duksctf

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.

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

download

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
Written on October 1, 2016