Wolv CTF 2024

Limited 1 [100 pts]

It’s pretty easy to find random integers if you know the seed, but what if every second has a different seed?

chal_time.py


We’re provided a Python source:

import time
import random
import sys

if __name__ == '__main__':
    flag = input("Flag? > ").encode('utf-8')
    correct = [189, 24, 103, 164, 36, 233, 227, 172, 244, 213, 61, 62, 84, 124, 242, 100, 22, 94, 108, 230, 24, 190, 23, 228, 24]
    time_cycle = int(time.time()) % 256
    if len(flag) != len(correct):
        print('Nope :(')
        sys.exit(1)
    for i in range(len(flag)):
        random.seed(i+time_cycle)
        if correct[i] != flag[i] ^ random.getrandbits(8):
            print('Nope :(')
            sys.exit(1)
    print(flag)

So, basically, the seed for the random.getrandbits(8) call is changing for each byte. This is very easily brute-forceable, by simply looping over all possible initial seeds (only 256 possible values), decrypting, and then checking is the result is all ASCII to limit the results that need to be manually checked.

import random

correct = [189, 24, 103, 164, 36, 233, 227, 172, 244, 213, 61, 62, 84, 124, 242, 100, 22, 94, 108, 230, 24, 190, 23, 228, 24]
randvals = []

for i in range(256 + len(correct)):
    random.seed(i)
    randvals.append(random.getrandbits(8))

for i in range(256):
    curr = b''
    isascii = True
    for j in range(len(correct)):
        x = correct[j] ^ randvals[i + j]
        if x >= 128:
            isascii = False
            break
        curr += chr(x).encode()
    if not isascii: continue
    print(curr)

Alternatively, since we know the prefix of the flag is wctf, we can check which initial seed values result in this prefix, and then easily decrypt from there.

import random

correct = [189, 24, 103, 164, 36, 233, 227, 172, 244, 213, 61, 62, 84, 124, 242, 100, 22, 94, 108, 230, 24, 190, 23, 228, 24]

seed = -1
for i in range(256):
    random.seed(i)
    f1 = correct[0] ^ random.getrandbits(8)
    random.seed(i + 1)
    f2 = correct[1] ^ random.getrandbits(8)
    if f1 == ord('w') and f2 == ord('c'):
        seed = i
        break

for j in range(len(correct)):
    random.seed(seed + j)
    print(chr(correct[j] ^ random.getrandbits(8)), end='')

Both scripts result in the flag!

wctf{f34R_0f_m1ss1ng_0ut}