PlaidCTF 2012 – Encryption Service [300] (Password Guessing)

We found the source code for this robot encryption service, except the key was redacted from it. The service is currently running at 23.21.15.166:4433

Summary: IV predict, byte-by-byte bruteforce

This challenge was based on a vulnerable crypto scheme with predictable (known) IV’s. First IV is random, but all the subsequent messages are encrypted with previous ciphertexts as IV’s (it’s important that we know IV before sending a message to encrypt).

Here’s client handler:

        iv = os.urandom(16)
        self.wfile.write(iv)
        while True:
            data = self.rfile.read(4)
            if not data:
                break
 
            try:
                length = struct.unpack('I', data)[0]
                if length > 4096:
                    break
                data = self.rfile.read(length)
                data += PROBLEM_KEY
                ciphertext = encrypt(data, iv)
                iv = ciphertext[-16:]
                self.wfile.write(struct.pack('I', len(ciphertext)))
                self.wfile.write(ciphertext)
            except:
                break

After we know some IV, we send this IV to encrypt and we get a ciphertext = AES(IV ^ IV) = AES(0). And the next IV is accurately AES(0), and the cool thing is that we can repeat it many times and IV will be the same.

After fixing the IV, we can bruteforce byte-by-byte the appended flag:
First encrypt

"X"*15 --> AES(IV, "X*15" + Flag[0]) + …

Then encrypt different chars until we get the same block as the first one:

"X"*15 + "A" --> AES(IV, "X*15" + "a") + …
"X"*15 + "B" --> AES(IV, "X*15" + "b") + …

Thus we can get the first byte of the flag, and so on.

Here’s exploit:

from sock import Sock
from struct import pack, unpack
 
ALPHA = "_abcdefghijklmnopqrstuvwxyz"
 
def send_msg(f, m):
	data = pack("I", len(m))
	data += m
	f.send(data)
 
	dlen = unpack("I", f.read_nbytes(4))[0]
	data = f.read_nbytes(dlen)
	return data, data[-16:]
 
f = Sock("23.21.15.166", 4433)
iv = f.read_nbytes(16)
 
key = ""
for index in xrange(len(key)+1, 99999):
	pad_len = 16 - (index % 16)
	msg = "A" * pad_len
 
	s, iv = send_msg(f, iv)
	s, iv = send_msg(f, msg)
 
	block_number = index / 16
	good_block = s[block_number * 16 : (block_number + 1) * 16]
	print "ENC", msg, "(%d)" % len(msg), "=", good_block[:16].encode("hex")
 
	for c in ALPHA:
		s, iv = send_msg(f, iv)
		msg = "A" * pad_len + key + c
		s, iv = send_msg(f, msg)
		block = s[block_number * 16 : (block_number + 1) * 16]
		if block == good_block:
			key += c
			print "Found", index-1, "char:", c, "key:", key
			break
	else:
		print "Finished\n"
		print "KEY:", key
		break
$ py predict_iv.py 
ENC AAAAAAAAAAAAAAA (15) = f1a42e850b6c7abaa7b53223470505c3
Found 0 char: p key: p
ENC AAAAAAAAAAAAAA (14) = 5edf4afc7bf0765bddba811a9b3adad6
Found 1 char: r key: pr
ENC AAAAAAAAAAAAA (13) = df41943880e2b461c31590e293202159
Found 2 char: e key: pre
ENC AAAAAAAAAAAA (12) = d9859acf09e49d50ada8e938c345f28d
Found 3 char: d key: pred
ENC AAAAAAAAAAA (11) = e5a8b4ee0ba44f72abeec767de8f1092
Found 4 char: i key: predi
...
ENC AAAAA (5) = be6ff4d4c812a4f2a3050e289bbbb98c
Found 26 char: o key: predictable_ivs_are_dangero
ENC AAAA (4) = 2b07c150317a5ac936817896c3846f24
Found 27 char: u key: predictable_ivs_are_dangerou
ENC AAA (3) = 17b9dad0127147163265627f1b5efeac
Found 28 char: s key: predictable_ivs_are_dangerous
ENC AA (2) = e681d4d39dee05ddf6c16e0ba9598288
Finished
 
KEY: predictable_ivs_are_dangerous

The flag: predictable_ivs_are_dangerous

Leave a Reply

Your email address will not be published.