chall.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
#!/usr/bin/env python3 import sys import os import random import binascii from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes, bytes_to_long from secret import FLAG IV = os.urandom(AES.block_size) KEY = os.urandom(AES.block_size) class Unbuffered(object): def __init__(self, stream): self.stream = stream def write(self, data): self.stream.write(data) self.stream.flush() def writelines(self, datas): self.stream.writelines(datas) self.stream.flush() def __getattr__(self, attr): return getattr(self.stream, attr) sys.stdout = Unbuffered(sys.stdout) def pad(msg): return msg + (chr(16 - len(msg) % 16) * (16 - len(msg) % 16)).encode() def get_flag(): flag = pad(FLAG) cipher = AES.new(IV, AES.MODE_ECB) flag = cipher.encrypt(flag) enc = b'' flag = pad(flag) iv = IV for i in range(0, len(flag), 16): cipher = AES.new(KEY, AES.MODE_CBC, iv) enc += cipher.encrypt(flag[i:i+16]) iv = long_to_bytes(bytes_to_long(enc[i:i+16]) ^ bytes_to_long(flag[i:i+16])) print('flag (in hex) =', binascii.hexlify(enc).decode()) def encrypt(): msg = input('msg (in hex) = ') if (len(msg) % 2 != 0): print('Invalid input!') return msg = binascii.unhexlify(msg.encode()) cipher = AES.new(KEY, AES.MODE_CBC, IV) enc = cipher.encrypt(pad(msg)) print('enc (in hex) =', binascii.hexlify(enc).decode()) def decrypt(): enc = input('enc (in hex) = ') if (len(enc) % 32 != 0): print('Invalid input!') return enc = binascii.unhexlify(enc.encode()) cipher = AES.new(KEY, AES.MODE_CBC, IV) msg = cipher.decrypt(enc) print('msg (in hex) =', binascii.hexlify(msg).decode()) def menu(): print('1. Get encrypted flag') print('2. Encrypt a message') print('3. Decrypt a message') print('4. Exit') if __name__ == '__main__': while True: try: menu() choice = input('> ') if choice == '1': get_flag() elif (choice == '2'): encrypt() elif (choice == '3'): decrypt() elif (choice == '4'): print('Bye.') break else: print('Invalid input!') except: print('Something went wrong.') break |
Summary
Seperti yang bisa kita lihat, codingan ini memberikan 3 pilihan yang bisa kita gunakan. Fitur pertama akan mem-print hasil enkripsi flagnya lewat fungsi khusus get_flag(). Fitur kedua memperbolehkan kita meng-encrypt string kita sendiri dengan key & IV yang sudah ditentukan oleh codingan. Fitur ketiga memperbolehkan kita men-decrypt encrypted text dengan key & IV yang juga sudah ditentukan oleh codingan. Key & IV berasal dari hasil os.random(16), jadi tiap session tentu akan berbeda.

Analisa get_flag()
Pertama, flag akan di-enkripsi metode AES_ECB dengan kuncinya menggunakan nilai IV. Setelah itu, hasil enkripsi ECB akan dibagi menjadi beberapa block berisi 16 byte, lalu tiap blocknya akan di-enkripsi metode AES_CBC dengan IV yang berubah-berubah. IV ini berasal dari hasil XOR antara block Plaintext (flag) dan block Ciphertext putaran sebelumnya. Jadi kira-kira penggambarannya seperti ini:
Cipher1 = [encrypt(Plain1, key, IV) di mana IV = default IV
Cipher2 = [encrypt(Plain2, key, IV1) di mana IV1 = Plain1 ⊕ Cipher1
Cipher3 = [encrypt(Plain3, key, IV2) di mana IV2 = Plain2 ⊕ Cipher2
…
Nah, karena hasil-hasil enkripsi block-block tersebut didasarkan pada hasil enkripsi block sebelumnya, maka kita harus memecahkannya dari block enkripsi pertama, yang masih menggunakan default IV sebagai IV nya.
Mencari Default IV
Saya menggunakan referensi https://github.com/joshuahaddad/CTF_WriteUps/blob/master/SwampCtf/swampThreeKeys.md untuk mencari IV-nya, yang mana di situ dijelaskan bahwa kita bisa mencari nilai IV dari custom plaintext dan hasil dekripsi dari plaintext tersebut. codingannya kira-kira seperti ini:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import sys import os import random import binascii from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes, bytes_to_long from pwn import * def getIV(r): r.sendline('2') r.sendline('616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161') r.recvuntil('enc (in hex) = ') decsample = r.recvline().strip() c0 = decsample[0:32] c1 = decsample[32:64] c2 = decsample[64:96] c3 = decsample[96:128] payload = c0+c1+c0+c3 r.sendline('3') r.sendline(payload) r.recvuntil('msg (in hex) = ') plain = r.recvline().strip() p0 = plain[0:32] p1 = plain[32:64] p2 = plain[64:96] p3 = plain[96:128] dkco = int(p2, 16)^int(c1, 16) iv = bytes.fromhex(hex(dkco ^ int(p0, 16))[2:]) print('default iv = ', iv) if __name__ == "__main__": r = remote('103.152.242.242', 10016) getIV(r) |
Output:

Oke, default IV sudah didapatkan, tinggal kita cari cara mendecrypt setiap block Ciphernya. Di sini kita akan memanfaatkan fitur ketiga yang sudah disediakan program, yaitu fitur decrypt. Untuk block Cipher pertama, dekripsi berjalan dengan aman dan kita juga bisa mendapatkan iv dari enkripsi selanjutnya.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
import sys import os import random import binascii from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes, bytes_to_long from pwn import * def decryptECB(key, msg): cipher = AES.new(key, AES.MODE_ECB) flag = cipher.decrypt(msg) return flag def getIV(r): r.sendline('2') r.sendline('616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161') r.recvuntil('enc (in hex) = ') decsample = r.recvline().strip() c0 = decsample[0:32] c1 = decsample[32:64] c2 = decsample[64:96] c3 = decsample[96:128] payload = c0+c1+c0+c3 r.sendline('3') r.sendline(payload) r.recvuntil('msg (in hex) = ') plain = r.recvline().strip() p0 = plain[0:32] p1 = plain[32:64] p2 = plain[64:96] p3 = plain[96:128] dkco = int(p2, 16)^int(c1, 16) iv = bytes.fromhex(hex(dkco ^ int(p0, 16))[2:]) print('default iv = ', iv) return iv def decrypting(r): r.sendline('1') r.recvuntil('flag (in hex) = ') flag = r.recvline().strip() r.sendline('3') r.sendline(flag[0:32]) r.recvuntil("msg (in hex) = ") firstPlainPart = r.recvline().strip()#flag didecrypt print(decryptECB(iv, binascii.unhexlify(firstPlainPart))) newivone = hex(int(firstPlainPart, 16)^int(flag[0:32], 16))[2:] print('newivone', newivone) if __name__ == "__main__": r = remote('103.152.242.242', 10016) iv = getIV(r) decrypting(r) |

Namun masalahnya, bagaimana kita melakukan dekripsi apabila IV yang digunakan dalam proses enkripsi Block Cipher2 berbeda dengan IV yang digunakan dalam fitur decrypt codingan (yang masih menggunakan default IV)? Mari kita lihat lagi proses dekripsi AES_CBC:
Plaintext = decrypt(message, KEY, IV)
Plaintext = decrypt(message, KEY) ⊕ IV
Objektif kita adalah men-XOR hasil dekripsi bukan dengan default IV, melainkan dengan newIV yang didapat dari block cipher ⊕ block plaintext putaran sebelumnya. Maka dari itu, kita gunakan default IV yang sudah kita dapat untuk “menghapus” efek XOR bawaan dari fitur decrypt dan kita XOR lagi dengan newIV yang kita punya.
Plaintext = (decrypt(message, KEY) ⊕ IV) ⊕ IV ⊕ newIV
Plaintext = decrypt(message, KEY) ⊕ newIV
Nah, tinggal tambahkan operasi ini ke kodingan dan lakukan sampai ke block cipher terakhir. Dapet deh flagnya
Full solve:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
import sys import os import random import binascii from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes, bytes_to_long from pwn import * def decryptECB(key, msg): cipher = AES.new(key, AES.MODE_ECB) flag = cipher.decrypt(msg) return flag def getIV(r): r.sendline('2') r.sendline('616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161') r.recvuntil('enc (in hex) = ') decsample = r.recvline().strip() c0 = decsample[0:32] c1 = decsample[32:64] c2 = decsample[64:96] c3 = decsample[96:128] payload = c0+c1+c0+c3 r.sendline('3') r.sendline(payload) r.recvuntil('msg (in hex) = ') plain = r.recvline().strip() p0 = plain[0:32] p1 = plain[32:64] p2 = plain[64:96] p3 = plain[96:128] dkco = int(p2, 16)^int(c1, 16) iv = bytes.fromhex(hex(dkco ^ int(p0, 16))[2:]) print('default iv = ', iv) return iv def decrypting(r): r.sendline('1') r.recvuntil('flag (in hex) = ') flag = r.recvline().strip() r.sendline('3') r.sendline(flag[0:32]) r.recvuntil("msg (in hex) = ") firstPlainPart = r.recvline().strip()#flag didecrypt print(decryptECB(iv, binascii.unhexlify(firstPlainPart))) newIV = hex(int(firstPlainPart, 16)^int(flag[0:32], 16))[2:] for i in range(32, len(flag), 32): r.sendline('3') r.sendline(flag[i:i+32]) r.recvuntil("msg (in hex) = ") decryptedGiven = r.recvline().strip()#flag didecrypt PlainPart = hex(int(decryptedGiven, 16) ^ int(binascii.hexlify(iv), 16) ^ int(newIV, 16))[2:]#decrypt^IV^newIV print(decryptECB(iv, binascii.unhexlify(PlainPart))) newIV = hex(int(PlainPart, 16)^int(flag[i:i+32], 16))[2:] if __name__ == "__main__": r = remote('103.152.242.242', 10016) iv = getIV(r) decrypting(r) |

Flag = COMPFEST13{y0u_aes_me_UpPpppPp_____t0_c0d3_on_st0rmy_Sea4aA5____e0212d1a34}