snakeCTF logo

Bloom Bloom

CRYPTO

1 file available


Analysis

The first thing to notice is the add_user method.


users = 0b0

def add_user(username):
    global users

    cipher = AES.new(key, AES.MODE_ECB)
    enc_username = cipher.encrypt(pad(username.encode(), AES.block_size))
    for i in range(hash_functions_count):
        digest = mmh3.hash(enc_username, i) % size
        users = users | (0x1 << digest)
    

Every time a new user is registered to the system, the users variable is updated by setting some of its bits.

On the other hand, by analyzing the check_user method:

def check_user(username):
    global users
    cipher = AES.new(key, AES.MODE_ECB)
    enc_username = cipher.encrypt(pad(username.encode(), AES.block_size))
    
    for i in range(hash_functions_count):
        digest = mmh3.hash(enc_username, i) % size
        if users & (0x1 << digest) == 0:
            return False
    
    return True

we can notice that a user exists within the users variable if and only if the bits are correctly set. Moreover, it does not check that the other bits are set to 0, then a user could be found even if it was not registered yet. For example, if the users variable is 011, it would recognize as registered users 001, 011. 010.

Solver

To exploit this issue, we can simply run a script that registers random users until the Administrator user is recognized as a valid user by the system. At that point, we can get the flag.

r = remote(HOST, PORT)
r.recvuntil(b">")

attempts_counter = 0
while True:
    attempts_counter += 1

    username = "".join([random.choice(alphabet) for _ in range(20)])
    r.sendline(b"3")
    r.sendlineafter(b"Username: ", username.encode())
    r.sendlineafter(b">", b"2")

    response = r.recvuntil(b">")
    if b"Here is your flag:" in response:
        print(response)
        print(f"Flag found in {attempts_counter} attempts")
        break

The flag

snakeCTF{w3lc0me_to_cryp70_ch4ll3ng35}