# PicoCTF - AES-ABC ###### tags: `PicoCTF` `CTF` `Crypto` ## Background [What is PPM file?](https://www.adobe.com/tw/creativecloud/file-types/image/raster/ppm-file.html) ## Source code :::spoiler Source Code ```python= #!/usr/bin/env python from Crypto.Cipher import AES from key import KEY import os import math BLOCK_SIZE = 16 UMAX = int(math.pow(256, BLOCK_SIZE)) def to_bytes(n): s = hex(n) s_n = s[2:] if 'L' in s_n: s_n = s_n.replace('L', '') if len(s_n) % 2 != 0: s_n = '0' + s_n decoded = s_n.decode('hex') pad = (len(decoded) % BLOCK_SIZE) if pad != 0: decoded = "\0" * (BLOCK_SIZE - pad) + decoded return decoded def remove_line(s): # returns the header line, and the rest of the file return s[:s.index('\n') + 1], s[s.index('\n')+1:] def parse_header_ppm(f): data = f.read() header = "" for i in range(3): header_i, data = remove_line(data) header += header_i return header, data def pad(pt): padding = BLOCK_SIZE - len(pt) % BLOCK_SIZE return pt + (chr(padding) * padding) def aes_abc_encrypt(pt): cipher = AES.new(KEY, AES.MODE_ECB) ct = cipher.encrypt(pad(pt)) blocks = [ct[i * BLOCK_SIZE:(i+1) * BLOCK_SIZE] for i in range(len(ct) / BLOCK_SIZE)] iv = os.urandom(16) blocks.insert(0, iv) for i in range(len(blocks) - 1): prev_blk = int(blocks[i].encode('hex'), 16) curr_blk = int(blocks[i+1].encode('hex'), 16) n_curr_blk = (prev_blk + curr_blk) % UMAX blocks[i+1] = to_bytes(n_curr_blk) ct_abc = "".join(blocks) return iv, ct_abc, ct if __name__=="__main__": with open('flag.ppm', 'rb') as f: header, data = parse_header_ppm(f) iv, c_img, ct = aes_abc_encrypt(data) with open('body.enc.ppm', 'wb') as fw: fw.write(header) fw.write(c_img) ``` ::: ## Recon 這一題也蠻有趣的,可以先看一下他怎麼加密的 1. 先把ppm file的header, data parse出來 2. 在51行用AES-ECB加密data,而我們知道ECB mode就很不安全 3. 在53行再把每一個block分出來並在開頭的地方插入initial vector 4. 57-62行的for-loop,就是把兩個block相加再mod UMAX就是對應的下一個block的值,意即: $$ c[0] \leftarrow Initial\ Vector=AES[0]\\ c[i+1] \leftarrow (AES[i+1]+c[i])\ \% \ 2^{128}\\ k*2^{128} \leftarrow AES[i+1]+c[i]-c[i+1],\ k \in \{0,1\}, \{AES[\ ], c[\ ]\} \in 2^{128} $$ 所以綜上所述,我們可以把ciphertext還原成AES的版本,這樣應該可以看到flag的一些資訊,即使不知道一開始的key也可以(從最後面的block算回來) $$ AES[i] \leftarrow k*2^{128}-c[i-1]+c[i] $$ ## Exploit ```python= #!/usr/bin/env python from Crypto.Cipher import AES # from key import KEY import os import math BLOCK_SIZE = 16 UMAX = int(math.pow(256, BLOCK_SIZE)) def to_bytes(n): s = hex(n) s_n = s[2:] if 'L' in s_n: s_n = s_n.replace('L', '') if len(s_n) % 2 != 0: s_n = '0' + s_n decoded = bytes.fromhex(s_n)#s_n.decode('hex') pad = (len(decoded) % BLOCK_SIZE) if pad != 0: decoded = b"\0" * (BLOCK_SIZE - pad) + decoded return decoded def remove_line(s): # returns the header line, and the rest of the file return s[:s.index(b'\n') + 1], s[s.index(b'\n')+1:] def parse_header_ppm(f): data = f.read() header = b"" for i in range(3): header_i, data = remove_line(data) header += header_i return header, data def pad(pt): padding = BLOCK_SIZE - len(pt) % BLOCK_SIZE return pt + (chr(padding) * padding) def abc_decrypt(ct): blocks = [ct[i * BLOCK_SIZE:(i+1) * BLOCK_SIZE] for i in range(len(ct) // BLOCK_SIZE)] k = 0 for idx in range(len(blocks)-1, 0, -1): curr_blk = int(blocks[idx].hex(), 16) prev_blk = int(blocks[idx-1].hex(), 16) if (k * UMAX + curr_blk - prev_blk) < 0: tmp = UMAX + curr_blk - prev_blk else: tmp = curr_blk - prev_blk blocks[idx] = to_bytes(tmp) pt_abc = b"".join(blocks) return pt_abc if __name__=="__main__": with open('body.enc.ppm', 'rb') as f: header, data = parse_header_ppm(f) pt_img = abc_decrypt(data) # iv, c_img, ct = aes_abc_encrypt(data) with open('body.dec.ppm', 'wb') as fw: fw.write(header) fw.write(pt_img) ``` ![](https://hackmd.io/_uploads/Hk18swavh.png) Flag: `picoCTF{d0Nt_r0ll_yoUr_0wN_aES}` ## Reference [AES-ABC Write up](https://github.com/Dvd848/CTFs/blob/master/2019_picoCTF/AES-ABC.md)