# [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 %}