Androids Encryption¶
Introduction¶
Solved by: hyperc.
Event: Pwn2Win 2020: https://ctftime.org/event/961
Challenge name: Androids Encryption (115 pts)
Description: We intercept an algorithm that is used among Androids. There are many hidden variables. Is it possible to recover the message?
File: server.py
Encryption logic¶
The challenge was accessible on a remote server which provided us with two options:
Get an encrypted version of a plaintext provided by the user
Get an encrypted version of the flag
The Python implementation of the server was also provided in server.py
and allowed us to understand the logic behind the encryption method.
The main method of this app is the encrypt
function.
Given a plaintext, a key and an initialisation vector (IV), it:
Encrypts the plaintext using AES algorithm in the PCBC mode.
Returns the ciphertext concatenated with the IV used.
def encrypt(txt, key, iv):
global key2, iv2
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 += aes.encrypt(xor(block, curr))
curr = xor(ctxt[-bs:], block)
iv2 = AES.new(key2, AES.MODE_ECB).decrypt(iv2)
key2 = xor(to_blocks(ctxt))
return str(base64.b64encode(iv+ctxt), encoding='utf8')
which can be illustrated by the following diagram:
The encrypt
function also does another important thing: it modifies two global variables, iv2
and key2
that are exactly the key and initialisation vector used by the application to return an encrypted version of the flag to the user.
key2 = xor(to_blocks(ctxt))
Hence, key2
is reinitialised after each encryption to the result of our previous encryption, which we obviously have.
Since this type of encryption is symmetric, getting key2
enables us to decrypt the encrypted flag.
Step by step¶
Send a random plaintext to the server and get the response ciphertext¶
response1 = 'qal7b3mi7fEvSccj+NcaYtqU4i4io4qT1g88K9wY2nQ='
iv_plus_ctext = base64.b64decode(response1)
ctext = al[16:] # IV is 16 bytes long
### Get key2 from the recevied ciphertext
key2 = xor(to_blocks(ctext))
Query the encrypted flag from the server¶
enc_flag = '36X0Ug8ZEIvrRDeus6c3GBynEY7La36H0/A1Bqoy87go8FyYOeRQOuN7b0fXJXMYqWZ9lo9MWkS8EaN9/8Tl7A=='
enc_flag = base64.b64decode(enc_flag)
Decrypt the ciphertext following the diagram above¶
from Crypto.Cipher import AES
iv2 = enc_flag[:16]
c1 = enc_flag[16:32]
c2 = enc_flag[32:48]
c3 = enc_flag[48:64]
aes = AES.new(key2, AES.MODE_ECB)
p1 = xor(aes.decrypt(c1),iv2)
p2 = xor(aes.decrypt(c2),xor(c1,p1))
p3 = xor(aes.decrypt(c3),xor(c2,p2))
And find the flag!¶
assert(p1+p2+p3 == b'CTF-BR{kn3W_7h4T_7hEr3_4r3_Pc8C_r3pe471ti0ns?!?}')