# [EN] Secure Store GPT ###### tags: `Writeup` `Crypto` `English` > [name=Curious] ## Train Of Thought ### Padding Oracle We will not discuss in detail the method and implementation of padding oracle here. For more information, please refer to [Github](https://github.com/Curious-Lucifer/CTFLib/blob/master/Crypto/Block_Cipher.py#L6) ``` ################################################################## # AES CBC Mode (Decrypt) # ################################################################## # # # ----------- ----------- ----------- # # | cipher1 |---* | cipher2 |---* | cipher3 | # # ----------- | ----------- | ----------- # # | | | | | # # ----------- | ----------- | ----------- # # | | | | | | | | # # | Decrypt | | | Decrypt | | | Decrypt | # # | | | | | | | | # # ----------- | ----------- | ----------- # # ------ | | | | | # # | IV | --> ⊕ *-------> ⊕ *-------> ⊕ # # ------ | | | # # ----------- ----------- ----------- # # | plain1 | | plain2 | | plain3 | # # ----------- ----------- ----------- # # # ################################################################## ``` Let's consider the working mechanism of padding oracle as shown in the diagram above. If we want to know the value of `plain3`, we need to know the values of `cipher2` and `cipher3`. Then, we can reduce the cipher to `cipher2 + cipher3` and obtain the value of `plain3` by brute-forcing each byte of `cipher2`. Similarly, if we want to know the value of `plain1`, we need to know the values of `IV` and `cipher1`. Again, we can reduce the cipher to `IV + cipher1` and obtain the value of `plain1` using the padding oracle. then we can simplify padding oracle into the form below, where `cipher[0]` represents the `IV`. ```python # i >= 1 plain[i] = padding_oracle(cipher[i - 1], cipher[i]) ``` --- ## Solution Let's analyze the `server.py` file. We can see that the challenge does not provide us with the value of `IV`, so we cannot obtain `plain1` using a regular padding oracle. However, the challenge provides us with a `token` and the CBC MAC of this token. If we draw out the algorithm for CBC MAC, it will look like this: ``` ################################################################## # AES CBC MAC # ################################################################## # # # ----------- ----------- # # | plain1 | | plain2 | # # ----------- ----------- # # ------ | | # # | IV | --> ⊕ *-------> ⊕ *---------* # # ------ | | | | | # # ----------- | ----------- | ----------- # # | | | | | | | | # # | Encrypt | | | Encrypt | | | Encrypt | # # | | | | | | | | # # ----------- | ----------- | ----------- # # | | | | | # # ----------- | ----------- | ----------- # # | cipher1 |---* | cipher2 |---* | CBC_MAC | # # ----------- ----------- ----------- # # # ################################################################## ``` In the diagram, `plain1 + plain2` represents the `token` that we have obtained. It is known that xor anything with a bytes string that consists of all b'\x00' of the same length will not change the value. Therefore, we can imagine the relationship between `cipher2` and `CBC_MAC` as follows: ``` ####################################### # # # ----------- ----------- # # | NULL |---* | CBC_MAC | # # ----------- | ----------- # # | | # # | ----------- # # | | | # # | | Decrypt | # # | | | # # | ----------- # # | | # # *-------> ⊕ # # | # # ----------- # # | cipher2 | # # ----------- # # # ####################################### ``` In this way, we can use the padding oracle to obtain the value of `cipher2`. Then, we can use the same method to obtain `xor(cipher1, plain2)` from the value of `cipher2`. Since we know the value of `token`, we can obtain the values of both `plain1` and `plain2`. Therefore, we can deduce the value of `cipher1`. Similarly, we can deduce the value of `xor(IV, plain1)` and finally obtain the value of `IV`. Once we know the value of `IV`, we can proceed with a regular padding oracle. We will not go into detail about it here. Solve Script : ```python= from pwn import * from base64 import b64encode, b64decode def padding_oracle_attack(pre_cipher_block: bytes, cipher_block: bytes, oracle, r): ''' - input : `pre_cipher_block (bytes)`, `cipher_block (bytes)`, `oracle (func)`, `r (remote object)` - output : `plain_block (bytes)` , cipher_block's plaintext - oracle func : - input : `cipher (bytes)`, `r (remote object)` - output : `padding_right (bool)` , represent if the padding of cipher's plaintext is right ''' assert len(pre_cipher_block) == len(cipher_block) == 16 last_bytes = [] for i in range(256): cipher_test = pre_cipher_block[:15] + bytes([i]) + cipher_block if oracle(cipher_test, r): last_bytes.append(bytes([i])) if len(last_bytes) == 1: plain_block = xor(last_bytes[0], b'\x01', pre_cipher_block[-1:]) else: plain_block = xor(last_bytes[last_bytes.index(pre_cipher_block[-1:]) ^ 1], b'\x01', pre_cipher_block[-1:]) print(str(plain_block), end='\r') for j in range(1,16): for i in range(256): cipher_test = pre_cipher_block[:15 - j] + bytes([i]) + xor(pre_cipher_block[-j:], plain_block, bytes([j + 1]) * j) + cipher_block if oracle(cipher_test, r): plain_block = xor(bytes([i]), bytes([j + 1]), pre_cipher_block[-j - 1:-j]) + plain_block break print(str(plain_block), end='\r') print(f'result : {str(plain_block)}') return plain_block r = remote('lotuxctf.com', 30001) r.recvlines(2) token_sig = b64decode(r.recvline().strip().split(b': ')[1].decode()) token, sig = token_sig[:-16], token_sig[-16:] flag_enc = b64decode(r.recvline().strip().split(b': ')[1].decode()) r.recvlines(1) REQTIME = 0 def oracle(cipher, r): global REQTIME REQTIME += 1 print(REQTIME) r.sendlineafter(b': ', b64encode(cipher)) return b'Message Stored' in r.recvline() pre_token = padding_oracle_attack(b'\x00' * 16, sig, oracle, r) pre_token = xor(padding_oracle_attack(b'\x00' * 16, pre_token, oracle, r), token[-16:]) iv = xor(padding_oracle_attack(b'\x00' * 16, pre_token, oracle, r), token[:16]) flag_enc = iv + flag_enc flag = b'' for i in range(0, len(flag_enc) - 16, 16): flag += padding_oracle_attack(flag_enc[i:i + 16], flag_enc[i + 16:i + 32], oracle, r) print(flag) r.interactive() ``` {%hackmd M1bgOPoiQbmM0JRHWaYA1g %}