N-AES

So, at first this seems literally impossible.

The rand_block function takes a random 1 byte seed(initialising it to the output of os.urandom(1) if the argument isnt given) and then generates a 16-byte random block from this.

The encryption is simple - given a stream of seed bytes and a plaintext, iterate through the seed bytes.

On each byte, set the current ciphertext variable to the current ciphertext variable encrypted with the key generated by the random block that is create using rand_block(cur_byte_of_seed)

Finally, the challenge is generated like so. A random base64 string is generated using urandom, then encrypted in the algorithm. We get the base64 of the encryption.

def gen_chall(text):
    text = pad(text, BLOCK_SIZE)
    for i in range(128):
        text = AES.new(rand_block(), AES.MODE_ECB).encrypt(text) # VULN

    return b64encode(text)

So, what's so fishy about this?

Notice no argument is passed to rand_block, it simply gets a rand block and encrypts 128 times.

The argument is supposed to be os.urandom(1) by default, right? The problem is, everytime you call the function with no argument, the os.urandom(1) isnt regenerated. Instead, the value is first generated when the function is defined and then set to that again and again and again.

Long story short - chall is generated with the same key 128 times. Since it's os.urandom(1), we'll only have to bruteforce 256 possible keys. That can be done easily, and we can check if we've decrypted correctly by checking if the padding matches.

From there, we can simply enter the decrypted ciphertext.

import os
os.environ['TERM'] = 'linux'
os.environ['TERMINFO'] = '/etc/terminfo'
from pwn import *
from base64 import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from os import urandom
from random import seed,randint
BLOCK_SIZE = 16
def rand_block(key_seed=urandom(1)):
    seed(key_seed)
    return bytes([randint(0, 255) for _ in range(BLOCK_SIZE)])
def gen_chall(text):
    text = pad(text, BLOCK_SIZE)
    for i in range(128):
        text = AES.new(rand_block(), AES.MODE_ECB).encrypt(text)
    return b64encode(text)
p = remote('167.172.123.213', 34567)
enc = p.recvline()[:-1]
#print(enc)
for byte in range(256):
    text = base64.b64decode(enc)
    for i in range(128):
        text = AES.new(rand_block(bytes([byte])), AES.MODE_ECB).decrypt(text)
    try:
        text = unpad(text,16)
        print(base64.b64encode(text))
    except:
        pass
p.interactive()

^ script prints out the needed base64, you'll have to manually enter it into the prompt to get the flag from there

Flag: rgbCTF{i_d0nt_7hink_7his_d03s_wh47_y0u_7hink_i7_d03s}

Last updated