LoginSignup
1
0

More than 3 years have passed since last update.

Pwn2Win Androids Encryption Write up

Last updated at Posted at 2020-06-01

Pwn2Win Androids Encryption Write up

Pwn2Win CTF was held on May 30-31 2020. this challenge is the easiest problem and the only one I solve in this competition.

Discreption

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

Source Code

We were provided source code as below.


#!/usr/bin/python3 -u
# *-* coding: latin1 -*-
import sys
import base64
from Crypto.Cipher import AES

from secrets import flag, key1, iv1


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)])


BUFF = 256
BLOCK_SIZE = 16
iv2 = AES.new(key1, AES.MODE_ECB).decrypt(iv1)
key2 = xor(to_blocks(flag))


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')


def enc_plaintext():
    print('Plaintext: ', end='')
    txt = base64.b64decode(input().rstrip())
    print(encrypt(txt, key1, iv1))


def enc_flag():
    print(encrypt(flag, key2, iv2))


def menu():
    while True:
        print('MENU')
        options = [('Encrypt your secret', enc_plaintext),
                   ('Encrypt my secret', enc_flag),
                   ('Exit', sys.exit)
                   ]
        for i, (op, _) in enumerate(options):
            print(f'{i+1} - {op}')
        print('Choice: ', end='')
        op = input().strip()
        assert op in ['1', '2', '3'], 'Invalid option'
        options[ord(op)-ord('1')][1]()


def main():
    print('Let\'s see if you are good enough in symmetric cryptography!\n')

    try:
        menu()
    except Exception as err:
        sys.exit(f'ERROR: {err}')


if __name__ == '__main__':
    main()

Challenge Summary

In summary, this encryption process is like the following image.
キャプチャ.PNG

After encryption, IV2 was decoded with AES-ECB using key2 which was regenerated using the encrypted text. we were given the encrypted text and previous key2.

As you can see, we can generate an arbitrary key2 by inputting an arbitrary plain text. With this key2 and previous IV2, we can newly presume IV2. Once we can get key2 and IV2, we can decrypt the flag.

Exploit

the exploit code is below.:point_down_tone2:

import base64
from pwn import *
from Crypto.Cipher import AES

BUFF = 256
BLOCK_SIZE = 16

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)])


p = remote("encryption.pwn2.win", 1337)
_ = p.recvuntil("Choice: ")

# firstly, i'll make an arbitrary key2 
p.sendline("1")
_ = p.recvuntil("Plaintext: ")
p.sendline(base64.b64encode(b"a"*16))
enc_m = str(p.recvline()).replace("\\n","")[2:-1]
enc_m = base64.b64decode(enc_m)
iv= enc_m[:BLOCK_SIZE]
ctxt = enc_m[BLOCK_SIZE:]
key2 = xor(to_blocks(ctxt))

# Secondly, get iv2
_ = p.recvuntil("Choice: ")
p.sendline("2")
enc_flag = str(p.recvline()).replace("\\n","")[2:-1]
enc_flag = base64.b64decode(enc_flag)
iv2= enc_flag[:BLOCK_SIZE]
ctxt = enc_flag[BLOCK_SIZE:]

# Next, culculate newly iv2,key2 using ctxt, key2 and iv2 above
iv2 = AES.new(key2, AES.MODE_ECB).decrypt(iv2)
key2 = xor(to_blocks(ctxt))

# Now we have iv2 and key2. let's decrypt the flag
_ = p.recvuntil("Choice: ")
p.sendline("2")
enc_flag = str(p.recvline()).replace("\\n","")[2:-1]
enc_flag = base64.b64decode(enc_flag)
ctxt = enc_flag[BLOCK_SIZE:]
b1 = xor(AES.new(key2, AES.MODE_ECB).decrypt(ctxt[:BLOCK_SIZE]) , iv2)

curr = xor(b1, ctxt[:BLOCK_SIZE])
b2 = xor(AES.new(key2, AES.MODE_ECB).decrypt(ctxt[BLOCK_SIZE:BLOCK_SIZE*2]) , curr)

curr = xor(b2, ctxt[BLOCK_SIZE:BLOCK_SIZE*2])
b3 = xor(AES.new(key2, AES.MODE_ECB).decrypt(ctxt[BLOCK_SIZE*2:BLOCK_SIZE*3]) , curr)

print(b1+b2+b3)
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0