Padding-oracle problem for, uhh, AES in “propagating cipher block chaining” mode. (Who comes up with these anyway?)

Server provides us with encrypted flag:

# The flag is padded with 16 bytes prefix
# flag = padding (16 bytes) + "SECCON{..."
ref_iv, ref_c = encrypt(flag)
print("I teach you a spell! repeat after me!")
print(base64.b64encode(ref_iv + ref_c).decode("utf-8"))

We can attempt to decrypt stuff on the form ref_c || <arbitrary> and we’ll get a message whether the padding is correct or not. Server uses a random key and we don’t get any other encrypted samples.

Playing around with this (unfamiliar) cipher, we observe what the Wikpedia page mentions, namely that swapping the order of two blocks doesn’t affect the subsequent blocks. But actually it’s more than that:

>>> dec(en[:3] + en[:3][::-1] + en[:3]) # we can reverse
[b'firstfirstfirstf', b'secondsecondseco', b'thirdthirdthirdt', b'/\xf7\x1cu\x8b\x90\xefi\xde\xac\x91\xa2\x9c\x85\xaa\xa9', b'x>\xcb,\x80\xf4\xa6\x0b\xbb\x8d\x8e\xcbO\xce\tW', b'\x95K!\xce-,\xc0\xc6h\xf7R\x96\xfcN\xdc\x91', b'firstfirstfirstf', b'secondsecondseco', b'thirdthirdthirdt']
>>> dec(en[:5] + rot(en[:5], 2) + en[:5]) # we can rotate...
[b'firstfirstfirstf', b'secondsecondseco', b'thirdthirdthirdt', b'carrotscarrotsca', b'applesapplesappl', b'\x91[\xe1\xf4\xaf#\x060\xf8\xb9iu\x1d#\xfc\x8e', b'\x86R\xfa\xf4\xa4#\x1d:\xeb\xafor\x00"\xfb\x9b', b'\x84C\xf8\xea\xae\x0f)\xfa\xb1xn\x15!\xe8\x96', b'\x9e\x10\x89\x8d\xc3\xbc\x15\xa8\xb0\x15\xb29\xc0\xe5\xb6\xa9', b'\x8b\x1c\x98\x91\xd9\xbe\x0f\xbf\xa0\x0e\xba4\xc1\xf3\xa1\xa0', b'firstfirstfirstf', b'secondsecondseco', b'thirdthirdthirdt', b'carrotscarrotsca', b'applesapplesappl']
>>> dec(en + shuffle(en) + en) # oh, any shuffle
[b'firstfirstfirstf', b'secondsecondseco', b'thirdthirdthirdt', b'carrotscarrotsca', b'applesapplesappl', b'orangesorangesor', b'\x8fS\x9f(L\xc68\x15\xf2X\xd2\xd9\x8eo\x16\xb5', b"l8\x93f'\xe5\xf3(\x017\x03x\x97\x80\x05\x91", b'\xd0\x85l\xf9\tQ\x08b6<\xec\xb3\x03%@\xbb', b'\xc8\xdb\xe8.\xaa#\xbf\x1fO\x9e7\x10m\x8e\xd4u', b'\xac\xb46e\x00:\x07\x06Nq\r\xa0.\xc0\x91', b'\x95K!\xce-,\xc0\xc6h\xf7R\x96\xfcN\xdc\x91', b'firstfirstfirstf', b'secondsecondseco', b'thirdthirdthirdt', b'carrotscarrotsca', b'applesapplesappl', b'orangesorangesor']

With this task I tried a new DSL-like approach rather than confusing myself with endless xor-algebra on paper. Doing block cipher algebra on paper always breaks my brain. I was tired and this way I don’t have to think, alright…

>>> rev_dec(en + shuffle(en[:3]) + en[:2])
'pp(0,) pp(1,) pp(2,) pp(3,) pp(4,) pp(5,) pp(0, 5)^en(5,) pp(1, 5)^en(5,) pp(2, 5)^en(5,) pp(0, 2, 5)^en(2, 5) pp(1, 2, 5)^en(2, 5)'
>>> rev_dec(en + en)
'pp(0,) pp(1,) pp(2,) pp(3,) pp(4,) pp(5,) pp(0, 5)^en(5,) pp(1, 5)^en(5,) pp(2, 5)^en(5,) pp(3, 5)^en(5,) pp(4, 5)^en(5,) en(5,)'

And with this we find the way to set up so we can use the oracle:

>>> rev_dec(en + [byte(1)*16, byte(1)*16])
'pp(0,) pp(1,) pp(2,) pp(3,) pp(4,) pp(5,) ??? 1^pp(5,)^en(5,)'
>>> rev_dec(en + en + en[:4] + [xor_bytes(en[3], byte(1)*16), xor_bytes(en[3], byte(1)*16)])
'pp(0,) pp(1,) pp(2,) pp(3,) pp(4,) pp(5,) pp(0, 5)^en(5,) pp(1, 5)^en(5,) pp(2, 5)^en(5,) pp(3, 5)^en(5,) pp(4, 5)^en(5,) en(5,) pp(0,) pp(1,) pp(2,) pp(3,) ??? 1^pp(3,)'

So we can do it byte-by-byte and select aribtrary blocks. That’s a lot of requests, and maybe there’s a better way to do it, but eh…