May
01

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