Rating:

### General idea

We had a server, which encrypt json file with AES-CBC with unusual padding, send it to us with IV and let us change it by sending encrypted bytes back.
Besides, server returns us different types of mistakes: decoding error and padding error.

json format: `{'admin': False, 'flag': open("flag1.txt").read()}`

We need to get 2 flags ( actually two parts of one ):

1st one - from json field 'flag'

2nd one will be given when we change `admin` from `False` to `True`

#### 1st flag

First of all, let's analyze json and split it into blocks. We will get something like:

```py
['\x02 {"admin": fals', 'e, "flag": "TetC','TF{***********"}']
```
Now it is clear that we need only last block.

Attack is very similar to [classical Padding-Oracle Attack on AES-CBC](https://robertheaton.com/2013/07/29/padding-oracle-attack/), but has some features, because:

1) padding function is unusual:

```py
PAD = bytes.fromhex("2019") * 8

def pad(s):
pad_length = 16 - len(s) % 16
return bytes([pad_length]) + PAD[:pad_length - 1] + s
```

2) plain text is padded at the beginning

To get to know 1st part of flag, we will send changed 3rd (penultimate, it will be IV for server) and 4st (last, it will be ciper-text) block of ciper-text.

We start from 1st byte of 3rd block.

1) Change the byte to \x00
2) Send the blocks to server
3) If we get answer 'incorrect padding', we change the byte to the next value and go to step 2.
4) If we got another answer, it means that we can calculate the byte from original text (flag byte)
5) Store this byte and go to change 2nd byte

This way we can get all block and recover the first flag byte by byte.

#### 2nd flag

We want make `obj['admin']` == `True`. Here server doesn't check flag accuracy, so we can just change `obj` to `{"admin": true}`. This json will take one block with minimal padding (`\x01`).

We will send two blocks: `IV^(original_2nd_block_pt)^(desired_2nd_block_pt)` and `encrypted 2nd block`

We don't know padding value in original plaintext, so we will try several variant until attack is successful. (`'\x02 '` will be correct)

### Exploits

#### 1st

```py
import json
from os import urandom
from pwn import remote, process
from string import ascii_letters, digits
from itertools import product

ADDR = "207.148.119.58", 5555
PAD = (("2019") * 8).decode('hex')

def get_paddings_dict(n):
ans = {}
for i in range(n):
ans[i] = pad(i+1)
return ans

def pad(n):
pad_length = n
return chr(pad_length) + PAD[:pad_length - 1]

def crack_byte(token, pos, i):
token[pos] = i
return ''.join('{:02x}'.format(x) for x in token)

def find_pad(r, token, pos, last_x):
'''
get bytes
'''
token = bytearray(token)
padings = get_paddings_dict(pos+1)
if pos:
token[0] = last_x[0] ^ ord(padings[0][0]) ^ ord(padings[pos][0])
for j in range(1, pos):
token[j] = last_x[j]

for i in range(256):
payload = crack_byte(token, pos, i)
r.sendline(payload)
ans = r.recvline()
if i % 64 == 0:
print("current step: ", pos, i, ans)
if 'padding' in ans:
continue
else:
return i
raise Exception("All padings are incorrect")

if __name__ == '__main__':
#r = remote(*ADDR)
r = process('./padwith2019.py')
token_hex = r.recvline(False)
print(token_hex)
token = token_hex.decode('hex')
parts = [token[i:i+16] for i in range(0, len(token), 16)]
known = "TF{***********"
flag = ''
exp_pad = pad(1)
c1 = parts[2]
c2 = parts[3]
last_x = []
for i in range(len(known)):
exp_pad = pad(i+1)
x = find_pad(r, c1+c2, i, last_x)
i2 = x ^ ord(exp_pad[i])
ch = chr(i2 ^ ord(c1[i]))
if i < len(known) and ch == known[i]:
flag += ch
print("Horay!", flag)
last_x.append(x)
else:
flag += ch
print("Is it right?", flag)
last_x.append(x)
print('TetC' + flag[:-2])
```

#### 2nd

```py
import json
from os import urandom
from pwn import remote, process
from string import ascii_letters, digits
from itertools import product

ADDR = "207.148.119.58", 5555

def crack(token):
test_token = bytearray(token)

test = b'\x02 {"admin": fals'
for i, (x, y) in enumerate(zip(test, b'\x01{"admin": true}')):
test_token[i] ^= ord(x) ^ ord(y)

return ''.join('{:02x}'.format(x) for x in test_token[:32])

if __name__ == '__main__':
#r = remote(*ADDR)
r = process('./padwith2019.py')
token = r.recvline(False).decode('hex')
new_token = crack(token)
r.sendline(new_token)
r.interactive()
```

Original writeup (https://github.com/amoniaka-knabino/CTF-writeups/tree/master/CTFs%202020/TetCTF/padwith2019).