Contents

Crypto - ECB oracle

Contents

Description

This challenge has been taken from http://aes.cryptohack.org/ecb_oracle/. What we have is an function that concats the FLAG to a plaintext given as parameter and encrypts it through EAS in ECB mode.

Here’s chall.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/python3

from flag import FLAG
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os

KEY = os.urandom(16)

def encrypt(plaintext):
    plaintext = plaintext.encode()
    # concat plaintext with 16 - len(plaintext) bytes 
    # with value 16 - len(plaintext)
    padded = pad(plaintext + FLAG.encode(), 16)
    cipher = AES.new(KEY, AES.MODE_ECB)
    try:
        encrypted = cipher.encrypt(padded)
    except ValueError as e:
        return {"error": str(e)}

    return encrypted.hex()

if __name__ == '__main__':
    while True:
        plain = input("encrypt> ")
        print(bytes.fromhex(encrypt(plain)))

Exploit

Let’s first take a look how AES ECB works:

./aes-ecb-enc.png

We can state that:

  • The flag is concatenated after the plaintext input;
  • ECB encodes blocks of 16 bytes with the same key;
  • The flag is 26 chars long.

The main idea to exploit this bad implementation is to start to encode a string of: $$\Bigl \lceil \dfrac{\text{flagSize}}{\text{blockSize}} \Bigr \rceil * \text{blockSize} - 1$$

bytes, so in this case of 31. If we do so, there’s the first char of the flag that gets encrypted in the last byte of the second block. In order to solve this challenge we can brute force this last char and then repeat the process for the remaining characters decreasing the input plaintext.

For instance, first plaintext that the oracle encrypts is the following one:

$$\text{Oracle}(\text{“A”} * 31 + \text{flag} + \text{padding}) \newline= E(\text{“A”} * 16) + E(\text{“A”} * 15 + ch_1) + E(ch_2 + … + ch_n + \text{padding})$$

Proceed finding the next chars decrementing the size of the “A”‘s until the entire flag is known.

Exploit.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os
from binascii import hexlify, unhexlify 
import string
from pwn import process

p = process("./chall.py")

def recvuntil_sendl(p, ustring, payload):
    p.recvuntil(ustring.encode())
    p.sendline(payload.encode())
    a = p.recvline().decode()
    # remove b and '' on the sides of the encrypted text 
    a = a[2:-1]   
    return list(bytes(a, "utf-8"))

def getflag(p):
    flag = ""
    for i in range(1, 27):
        payload = "A" * (32 - i)
        answer = recvuntil_sendl(p, "encrypt> ", payload) 
        
        # brute force the char
        for s in string.printable:
            bf = payload + flag + s

            bf_answer = recvuntil_sendl(p, "encrypt> ", bf)

            # each hex is two char so compare until 32 * 2
            if bf_answer[0:32 * 2] == answer[0:32 * 2]:
                flag = flag + s
                break

    print(flag)


getflag(p)
p.close()

python3 exploit.py

[+] Starting local process ‘./chall.py’: pid 38213
flag{4tt3nt1_4d_u54r3_3cb}
[*] Stopped process ‘./chall.py’ (pid 38213)