Rating: 5.0
# We Three Keys
![](./brief.png)
The challenge provides the code used on the netcat server. I've redacted some code for legibility purposes.
```python
#!/usr/bin/env python2
from Crypto.Cipher import AES
from keys import key1, key2, key3
def main():
print("Hello! We present you with the future kings, we three keys!")
print("Pick your key, and pick wisely!")
key_opt = str(raw_input("<= "))
if key_opt == "1":
key = key1
elif key_opt == "2":
key = key2
elif key_opt == "3":
key = key3
else:
print("Come on, I said we have 3!")
exit()
while True:
print("1) Encrypt a message")
print("2) Decrypt a message")
print("3) Choose a new key")
print("4) Exit")
choice = str(raw_input("<= "))
if choice == "1":
encrypt_message(key, key)
elif choice == "2":
decrypt_message(key, key)
elif choice == "3":
key = new_key()
else:
exit()
if __name__=='__main__':
main()
```
The key element here is the use of the key as the IV!
```python
encrypt_message(key, key)
```
This opens up the encryption up to the ```key == IV``` attack.
### Exploit
Because we can choose plaintext to be encrypted we have a Chosen Plaintext Attack **CPA**.
The attack begins by encrypting at least 3 blocks of plaintext
```
AES_ENCRYPT(P1, P2, P3) = C1, C2, C3
```
Then taking the ```C1``` to produce a special payload
```
CT' = C1 0 C1
```
We then take this ciphertext and decrypt it.
This then gives us:
```
PT1' PT2' PT3'
```
And:
```
PT1' ⊕ PT3' == IV (Key)
```
This relationship is due to how AES-CBC mode operates.
![](./CBC_decryption.png)
This means when we XOR ```PT1'``` and ```PT3'```
```
(Decrypt[CT1] ⊕ KEY) ⊕ (Decrypt[CT1]) == KEY
```
Meaning we've been able to extract the key!
The exploit code can be seen below:
```python
from pwn import *
import re
def split_in_blocks(ct):
return re.findall(".{32}", ct)
def hexXor(a, b):
"""
Produces an XOR result of two equal length Hex values
"""
if len(a) != len(b):
raise("Error: incorrect length in hexXor")
length = len(a)
binA = bin(int(a, 16))[2:].zfill(length)
binB = bin(int(b, 16))[2:].zfill(length)
xor = int(binA, 2) ^ int(binB, 2)
# Format ensures that the hex values are always the same length
hexOutput = format(xor, f"#0{length + 2}x")[2:]
return hexOutput
def get_key(keyNumber, payload):
r.recvuntil("<= ")
r.sendline(f"{keyNumber}")
r.recvuntil("<= ")
r.sendline("1")
r.recvuntil("<= ")
r.sendline(payload)
ct = r.read().decode("utf-8")
# Hacked cipher text
ct_blocks = split_in_blocks(ct)
ct_blocks[1] = "00" * 16
ct_blocks[2] = ct_blocks[0]
r.recvuntil("<= ")
r.sendline("2")
r.recvuntil("<= ")
r.sendline("".join(ct_blocks))
ct_prime = r.read().decode("utf-8")
ct_prime_blocks = split_in_blocks(ct_prime)
key = hexXor(ct_prime_blocks[0], ct_prime_blocks[2])
r.recvuntil("<= ")
r.sendline("3")
return key
r = remote("chal1.swampctf.com", 1441)
# Creation of the faked CT
pt = "41" * 16
payload = pt + pt + pt
flag = ""
flag += get_key(1, payload)
flag += get_key(2, payload)
flag += get_key(3, payload)
print(flag)
```
This gives us the output:
```
666c61677b7730775f776834745f6c347a795f6b33797a5f6d7563685f7733346b5f6372797074305f6634316c73217d
```
When decode from hex gives us the flag:
```
flag{w0w_wh4t_l4zy_k3yz_much_w34k_crypt0_f41ls!}
```