# Androids Encryption (Crypto) - Pwn2Win CTF 2020

My writeup for Androids Encryption challenge in the Pwn2Win CTF 2020

### crypto 115 - 108 solves

We intercept an algorithm that is used among Androids. There are many hidden variables. Is it possible to recover the message?

Author: andre_smaira

Server: nc encryption.pwn2.win 1337

On connecting to the challenge service, we are given two options -

Also, in the server.py file, we see there are two functions, `enc_plaintext`

and `enc_flag`

. Both these functions call the `encrypt`

function.

The `enc_plaintext`

functions calls `encrypt`

with the plaintext supplied by the user (encoded in base64), key1 and iv1 (which are secret values) as arguments.

The `enc_flag`

fucntion takes as arguments the secret flag and iv2 and key2 which are actually derived from iv1, key1 and the flag.

```
iv2 = AES.new(key1, AES.MODE_ECB).decrypt(iv1)
key2 = xor(to_blocks(flag))
```

The `encrypt`

function returns the base64 encoding of the (iv+ciphertext) string.

### Exploit

We see that every time `encrypt`

is called, the key2 and iv2 values are updated. iv2 becomes the AES-ECB decryption of the old iv2 and key2 is now the first block of the ciphertext (since the xor function with one argument simply returns the first block of the argument). So, our goal is now to recover the key2 and iv2 values so we can then reverse the `encrypt_flag`

function and recover the flag.

If we call `encrypt_flag`

(Choice 2) the first time, then the new key2 value will be the first block of the ciphertext. Nnow during this, the iv2 has also been updated but we don’t know that value, since the first part of the returned value, is the old iv2.

So, what we do next is call the `encrypt_flag`

(Choice 2) function again, then we get the new iv2 value along with the ciphertext. This means taht now we know the iv2 and the key2 value taht was used to encrypt the flag to obtain the ciphertext. What remains now, is just to reverse the `encrypt`

function and call it with our values of the ciphertext, key2 and iv2. This will get us the flag.

The `decrypt`

function can be written as -

```
assert len(key) == BLOCK_SIZE, f'Invalid key size'
assert len(iv) == BLOCK_SIZE, 'Invalid IV size'
assert len(txt) % BLOCK_SIZE == 0, 'Invalid plaintext size'
bs = len(key)
blocks = to_blocks(txt)
ctxt = b''
aes = AES.new(key, AES.MODE_ECB)
curr = iv
for block in blocks:
ctxt += xor(curr, aes.decrypt(block)) # Inverse of the encrypt function
curr = xor(ctxt[-bs:], block)
return ctxt
```

The complete exploit code is shown below -

```
from pwn import *
import base64
from Crypto.Cipher import AES
p = remote("encryption.pwn2.win", 1337)
# context.log_level = 'debug'
BUFF = 256
BLOCK_SIZE = 16
key2 = None
iv2 = None
def to_blocks(txt):
return [txt[i*BLOCK_SIZE:(i+1)*BLOCK_SIZE] for i in range(len(txt)//BLOCK_SIZE)]
def xor(b1, b2=None):
if isinstance(b1, list) and b2 is None:
assert len(set([len(b) for b in b1])) == 1, 'xor() - Invalid input size'
assert all([isinstance(b, bytes) for b in b1]), 'xor() - Invalid input type'
x = [len(b) for b in b1][0]*b'\x00'
for b in b1:
x = xor(x, b)
return x
assert isinstance(b1, bytes) and isinstance(b2, bytes), 'xor() - Invalid input type'
return bytes([a ^ b for a, b in zip(b1, b2)])
def decrypt(txt, key, iv):
assert len(key) == BLOCK_SIZE, f'Invalid key size'
assert len(iv) == BLOCK_SIZE, 'Invalid IV size'
assert len(txt) % BLOCK_SIZE == 0, 'Invalid plaintext size'
bs = len(key)
blocks = to_blocks(txt)
ctxt = b''
aes = AES.new(key, AES.MODE_ECB)
curr = iv
for block in blocks:
ctxt += xor(curr, aes.decrypt(block)) # Inverse of the encrypt function
curr = xor(ctxt[-bs:], block)
return ctxt
def encrypt_flag(p):
p.recvuntil(b"Choice: ")
p.sendline(b"2")
x = p.recvline().strip()
y = base64.b64decode(x)
return y[:16], y[16:]
def enc_plaintext(p, plaintext):
p.recvuntil(b"Choice: ")
p.sendline(b"1")
p.recvuntil(b"Plaintext: ")
p.sendline(plaintext)
x = p.recvline().strip()
y = base64.b64decode(x)
return y[:16], y[16:]
def getDecoding(s):
y = base64.b64decode(s)
return y[:16], y[16:]
iv2_orig, flag_enc_orig = encrypt_flag(p)
print(iv2_orig)
print(flag_enc_orig)
key2_new = xor(to_blocks(flag_enc_orig))
print(b"Key2 :" + key2_new)
iv2_new, flag_enc_cipher_new = encrypt_flag(p)
print(b"iv2 :", iv2_new)
print(b"flag_cipher_new :", flag_enc_cipher_new)
print(decrypt(flag_enc_cipher_new, key2_new, iv2_new).decode())
```

Running this, prints the flag - **CTF-BR{kn3W_7h4T_7hEr3_4r3_Pc8C_r3pe471ti0ns?!?}**

And yeah, after reading the flag, I realised it was actually AES in PCBC mode.