Post

CHCCTF Random Writeup

Given:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/usr/local/bin/python
import random
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
print("Welcome to my maze that changes everytime")
flag = open('./flag.txt','rb').read().strip()
def menu():
    print("""
          Choose an option:
          1. Get Random Data
          2. Get Flag
          3. Exit """)
    
def second_exists(num):
    random.seed(int(os.urandom(64).hex(),16))
    key = b''
    intermediate = random.randbytes(1)
    for i in range(16):
        key += intermediate
        random.seed(int(intermediate.hex(),16))
        intermediate = random.randbytes(1)
    random.seed(num)
    newkey = int.from_bytes(random.randbytes(16)) ^ int.from_bytes(key)
    newkey = newkey.to_bytes(len(key) , 'big')
    enc_flag = encrypt_flag(flag,newkey)
    print(f"Encrypted Flag in hex: {enc_flag.hex()}")

def encrypt_flag(message,key):
    cipher = AES.new(key, AES.MODE_ECB)
    ciphertext = cipher.encrypt(pad(message, AES.block_size))
    return ciphertext

while True:
    menu()
    try:
        data = int(input("Enter your choice: "))
    except ValueError:
        print("Invalid input. Please enter a number.")
        continue
    if data == 1:
        total = []
        try:
            i = int(input("How many random numbers do you want: "))
        except ValueError:
            continue
        for x in range(i):    
            total.append(random.getrandbits(32))
        print(total)

    elif data == 2:
        print("Not so fast bro, just answer a simple question")
        print("Guess a number which I am thinking from 1-10000000000")
        num = random.randint(1,10000000000)
        try:
            guess = int(input("Your guess: "))
        except ValueError:
            print("Invalid input. Please enter a number.")
            continue
        
        if guess == num:
            print("You guessed right")
            second_exists(num)
            exit()
        else:
            print("You guessed wrong")
            
            exit()
    else:
        
        exit()

The particular vulnerability exists in the code ability to give us as many random numbers we want, due to pseudo randomness we can predicted the next generated random number huh not so random after all

for this will be using the randcrack to predict the next number to break the pseudo randomness we need to generate 624 random numbers

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
import random
from randcrack import RandCrack
from Crypto.Cipher import AES

from pwn import *

con = remote('ctf.cyberhacktivators.club', 32952)


con.sendlineafter(b"Enter your choice: ", "1")
con.sendlineafter(b"How many random numbers do you want: ", b"624")

random_numbers = con.recvuntil(b']').decode()[1:-1].split(", ")
#print(random_numbers)

random_numbers = [int(i) for i in random_numbers]

print(len(random_numbers))
rc = RandCrack()

for i in range(624):
    rc.submit(random_numbers[i])


predicted = rc.predict_randint(1, 10000000000)

this is successfully predict the next number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
elif data == 2:
        print("Not so fast bro, just answer a simple question")
        print("Guess a number which I am thinking from 1-10000000000")
        num = random.randint(1,10000000000)
        try:
            guess = int(input("Your guess: "))
        except ValueError:
            print("Invalid input. Please enter a number.")
            continue
        
        if guess == num:
            print("You guessed right")
            second_exists(num)
            exit()

when we press 2 this will generate a new random number which will be equal to the predicted we predicted using the randcrack predict_randint

this passes the first check

now on to the second function that encrypts

1
2
3
4
5
6
7
8
9
10
11
12
13
def second_exists(num):
    random.seed(int(os.urandom(64).hex(),16))
    key = b''
    intermediate = random.randbytes(1)
    for i in range(16):
        key += intermediate
        random.seed(int(intermediate.hex(),16))
        intermediate = random.randbytes(1)
    random.seed(num)
    newkey = int.from_bytes(random.randbytes(16)) ^ int.from_bytes(key)
    newkey = newkey.to_bytes(len(key) , 'big')
    enc_flag = encrypt_flag(flag,newkey)
    print(f"Encrypted Flag in hex: {enc_flag.hex()}")

intermediate = random.randbytes(1) this only generates one byte and in the loop AES key is constructed using this intermediate

we can easily create a bruteforce script to create the correct key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for i in range(0xff):
    key = b''
    for j in range(16):
        key += i.to_bytes(1, 'big')
        random.seed(int.from_bytes(key, 'big'))
        intermediate = random.randbytes(1)
    random.seed(predicted)
    newkey = int.from_bytes(random.randbytes(16), 'big') ^ int.from_bytes(key, 'big')
    newkey = newkey.to_bytes(len(key) , 'big')
    cipher = AES.new(newkey, AES.MODE_ECB)
    plaintext = cipher.decrypt(enc_flag)
    if b'CHC' in plaintext:
        print(plaintext)
        break

solve script:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import random
from randcrack import RandCrack
from Crypto.Cipher import AES

from pwn import *

con = remote('ctf.cyberhacktivators.club', 32952)


con.sendlineafter(b"Enter your choice: ", "1")
con.sendlineafter(b"How many random numbers do you want: ", b"624")

random_numbers = con.recvuntil(b']').decode()[1:-1].split(", ")
#print(random_numbers)

random_numbers = [int(i) for i in random_numbers]

print(len(random_numbers))
rc = RandCrack()

for i in range(624):
    rc.submit(random_numbers[i])


predicted = rc.predict_randint(1, 10000000000)
con.sendlineafter(b"Enter your choice: ", "2")
con.sendlineafter(b"Your guess: ", str(predicted))

print(predicted)
enc_flag = con.recvall().strip().split(b": ")[1]
print(enc_flag)

enc_flag = bytes.fromhex(enc_flag.decode())

print(enc_flag)

key = b''

for i in range(0xff):
    key = b''
    for j in range(16):
        key += i.to_bytes(1, 'big')
        random.seed(int.from_bytes(key, 'big'))
        intermediate = random.randbytes(1)
    random.seed(predicted)
    newkey = int.from_bytes(random.randbytes(16), 'big') ^ int.from_bytes(key, 'big')
    newkey = newkey.to_bytes(len(key) , 'big')
    cipher = AES.new(newkey, AES.MODE_ECB)
    plaintext = cipher.decrypt(enc_flag)
    if b'CHC' in plaintext:
        print(plaintext)
        break



flag:

1
CHC{rand0m5_ar3_fun_hT6eZ0lXBrfK_unl3ss_in_0wn_c0d34DgzKXmm}
This post is licensed under CC BY 4.0 by the author.