100% Robust PRNG
Let’s try to create an unhackable of pseudo-random number (PRN) of the Python interpreter. We assume that the PRNs are generated by the getrandbits(31) function of the standard random library. You can ask for several consecutive PRNs, up to 1000 pieces. And then you have to guess the next PRN.
Server code
import timeimport randomimport math
flag = "FAKE_FLAG"
m = random.getrandbits(31)a = 2**10b = 2**30
rand_numbers = []
d = """Попробуем взломать генератор псевдослучайных чисел (ПСЧ) Python.Считаем, что ПСЧ создаются функцией getrandbits(31) стандартной библиотеки random.Вы можете попросить несколько последовательных ПСЧ, до 1000 штук.А затем вы должны угадать следующее ПСЧ."""print (f"{d}\n\n")
print ('1. Получить следующее число')print ('2. Угадать следующее число')ind = 0
while True: if ind == 1000: print ('Слишком долго думаешь. Отдохни ...') break inp = input('> ') if inp == '1': print (f"Следующее число: {random.getrandbits(31)}") elif inp == '2': ans = int(input('Ваше число: ')) my_ans = random.getrandbits(31) if ans == my_ans: print (f"\nФлаг: {flag}") else: print (f"\nОшибка. Мое число: {my_ans}") break ind += 1
Code is pretty similar to the last chall but this time its not choosing a specific seed. This means it is prob getting it from /dev/urandom
which is impossible to guess
So this time we need to get a bunch of inputs and crack the Mersenne Twister (the PRNG python uses). Usually all we need are 624 inputs of 32 bit numbers and we are able to guess the next number. However this time we only get 31 bits which makes it challenging since we are missing a bit everytime
Now a lot of crackers found online dont support less than 32 bits but I was able to find one that does any number of bits by using z3 to crack the Mersenne Twister here
Solution
import mathimport timeimport randomfrom pwn import *from mt19937_crack import RandomSolver
def getnextguess(l): randomSolver = RandomSolver() for i in l: randomSolver.submit_getrandbits(i, 31) randomSolver.solve() return randomSolver.getrandbits(31)
io = remote('ctf.mf.grsu.by', 9046)output = io.readuntil(b'>')l = []for i in range(999): io.sendline("1") o = io.recvline() num = int(o.decode().split(': ')[1].strip(" \n'")) print(i, num) l.append(num)
print("NEXT NUM: ", getnextguess(l))io.interactive()
Code is pretty much the same, we just grab 1000 inputs, submit it to the randomsolver and make it to solve. Then we get our next generated number
Since we only get 1000 inputs it can get pretty inaccurate (we should need about 624*2 = 1248 inputs) but if we try a few times we can get lucky and get the flag
...995 712169511996 24678483997 945543443998 1180336752NEXT NUM: 818870482[*] Switching to interactive mode> $ 2Ваше число: $ 818870482
Флаг: grodno{0ebcd057543753ee54f3a077c6644953923237233143562f0e60}
Conclusion
Again pythons PRNG can be easily hacked so don’t use it for things that need to be secure