WORMCON 0x01 2021 - Exclusive

Cryptography – 100 pts (25 solves) – Chall author: BUILDYOURCTF

A home-rolled XOR cipher with a stunning key space of 1 byte, yes a total of 256 different keys. That just asks to be brute-forced and so that is exactly what we will do. Try out all the keys and get our flag.

Check out write-ups by my teammates on K3RN3L4RMY.com

Exploration

Take a look at the source code. More specifically, can you spot the vulnerability without understanding the encryption process at all?

def splitit(n):
    return (n >> 4), (n & 0xF)

def encrypt(n, key1, key2):
    m, l = splitit(n)
    e = ((m ^ key1) << 4) | (l ^ key2)
    return e

FLAG = open('flag.txt').read().lstrip('wormcon{').rstrip('}')
alpha = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'

assert all(x in alpha for x in FLAG)

otp = int(os.urandom(1).hex(), 16)
otpm, otpl = splitit(otp)

print(f"{otp = }")
cipher = []

for i,ch in enumerate(FLAG):
    if i % 2 == 0:
        enc = encrypt(ord(ch), otpm, otpl)
    else:
        enc = encrypt(ord(ch), otpl, otpm)
    cipher.append(enc)

cipher = bytes(cipher).hex()
print(f'{cipher = }')

open('out.txt','w').write(f'cipher = {cipher}')

The encryption function isn’t secure at all, but the biggest problem lies in the following line.

otp = int(os.urandom(1).hex(), 16)

This ‘otp’ variable is used as the key of the encryption process and it is ONLY 1 BYTE LONG! This means that there are only 256 different keys possible, this is way too few and can be easily brute-forced by a computer. In fact, you might even be able to brute-force this on pen and paper within CTF time.

Exploitation

The brute-force is quite easily set up as we just copy-paste most of the source code but replace the random key generation with a for-loop that iterates over all possible key values, 0 to 255. Somewhere amongst the output will we find our flag! You could make it a bit easier for yourself by adding an if-statement to the final print call that check for the presence of the substring ‘wormcon’ (flag format).

def splitit(n):
	return (n >> 4), (n & 0xF)

def encrypt(n, key1, key2):
	m, l = splitit(n)
	e = ((m ^ key1) << 4) | (l ^ key2)
	return e

alpha = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'

cipher = "a1adabc2b7acbbffb5ae86fee8edb1aeabc2e8a886a986f5e9f0eac2bbefeaeaeaf986fee8edb1aeab"

for k in range(256):
    otpm, otpl = splitit(k)
    
    plain = []

    for i,ch in enumerate(bytes.fromhex(cipher)):
        if i % 2 == 0:
            enc = encrypt(ch, otpm, otpl)
        else:
            enc = encrypt(ch, otpl, otpm)
        plain.append(enc)

    plain = bytes(plain)
    print(plain)

Ta-da!

wormcon{x0r_n1bbl3_c1ph3r_15_4_h0m3_br3w3d_c1ph3r}