# Simple Crypto - 0x04(2023 Lab - POA) ## Background [ Crypto I - Timmy](https://youtu.be/dYyNeMeDM20?si=BEvBPBzCsg8oWv_Q&t=8317) ## Source Code :::spoiler Source Code ```python #! /usr/bin/python3 from Crypto.Cipher import AES import os from secret import FLAG def pad(data, block_size): data += bytes([0x80] + [0x00] * (15 - len(data) % block_size)) return data # padding style: <oooooo[0x80][0x00]...[0x00]> (find first [0x80]) def unpad(data, block_size): if len(data) % block_size: raise ValueError padding_len = 0 for i in range(1, len(data) + 1): if data[-i] == 0x80: padding_len = i break elif data[-i] != 0x00: raise ValueError else: raise ValueError return data[:-padding_len] key = os.urandom(16) cipher = AES.new(key, AES.MODE_CBC) ct = cipher.encrypt(pad(FLAG, AES.block_size)) iv = cipher.iv print((iv + ct).hex()) # same encryption while True: try: inp = bytes.fromhex(input().strip()) # hex style input iv, ct = inp[:16], inp[16:] # get first 16 bytes from input cipher = AES.new(key, AES.MODE_CBC, iv) pt = unpad(cipher.decrypt(ct), AES.block_size) print("Well received :)") except ValueError: print("Something went wrong :(") ``` ::: ## Recon 這一題是簡單的padding oracle attack,他一樣是應用在CBC mode上,只是他padding的方式和上課教的有一點不一樣,他會先在最後放一個0x80然後接續放0x00直到長度%16==0,同樣的,我們可以用上課教的方式: * What we have: 我們有的東西就是密文,所以可以利用它動一些手腳 * Our Goal 1: 目標是要取得原本和47進行XOR的數字是多少 * Our Goal 2: 這樣才可以取得最後的明文69 ![](https://hackmd.io/_uploads/r1p3yoGlp.png) * How to achieve: 我們可以簡單猜一個byte,從0x00開始,把密文換成猜測的byte,這樣256種組合和原本的Goal 1所求的byte進行XOR後會padding正確(也就是0x01),此時假設我們已經猜到目前是0x2f符合padding正確的目標,代表現在的假明文是0x01,則原本和0x47進行XOR的數字就是0x01⊕0x2f,然後我們就可以回到原本解密的流程,也就是原本的密文0x47⊕剛剛得知的(0x01⊕0x2f),就會得到想要的正確的明文0x69 ![](https://hackmd.io/_uploads/H1yKboMlp.png) 所以套用到今天的lab意思也是一樣,如果要知道padding是否正確可以問oracle,反正只要符合明文+0x80+(0...15)\*0x00,這一題的flag長度可以從題目給的ciphertext看出來,顯然扣掉16bytes的initial vector後,flag的長度是32 bytes,也就是說我們從第二個block開始解,我們可以單獨把第一個ciphertext block當成第二個ciphertext block的initial vector,合併後再一起送出去,然後不斷變化IV的最後一個byte,如果oracle回傳`Well received :)`代表第一個bytes猜對了,我們就可以把flag的最後一個bytes求出來$\to$我們猜的byte⊕原本ciphertext的最後一個byte⊕0x80(0x80是我們判斷padding正確的依據),當然找到真正的plaintext byte後要把我們猜測的block恢復原狀,接著繼續找下一個byte ## Exploit :::spoiler Whole Exploit Script ```python import sys from pwn import * from tqdm import trange p = remote('edu-ctf.zoolab.org',10004) # p = process(['python', './POA_4af88990ab364609.py']) ct = p.readline()[:-1].decode() ct = bytes.fromhex(ct) iv, ct1, ct2 = ct[:16], ct[16:32], ct[32:48] flag = bytearray(32) index = 31 count1 = 0 _iv, _ct1, _ct2 = bytearray(ct[:16]), bytearray(ct[16:32]), bytearray(ct[32:48]) for i in range(15, -1, -1): count2 = count1 count1 = 0 for j in range(256): _ct1[i] = j p.sendline(bytearray.hex(_ct1+_ct2).encode()) reply = p.readline()[:-1].decode() if reply == 'Well received :)': count1 += 1 if j != ct1[i]: flag[index] = ct1[i] ^ _ct1[i] ^ 128 if abs(count1 - count2) == 1: flag[index] = 128 _ct1[i] = 0 ^ flag[index] ^ ct1[i] index -= 1 _iv, _ct1, _ct2 = bytearray(ct[:16]), bytearray(ct[16:32]), bytearray(ct[32:48]) for i in range(15, -1, -1): for j in range(256): _iv[i] = j p.sendline(bytearray.hex(_iv+_ct1).encode()) reply = p.readline()[:-1].decode() if reply == 'Well received :)': flag[index] = _iv[i] ^ iv[i] ^ 128 break _iv[i] = 0 ^ flag[index] ^ iv[i] index -= 1 print(bytes(flag)) ``` :::