NOTE: This writeup is a valid solution to both dream and dream-revenge. There was a cheese in dream which I did not notice, leading me to take the long road and find the intended solution.
We are given a script which encrypts a flag with AES. Before encrypting the flag, the program takes in a list of 8 indexes as inputs. It then randomly generates 624 numbers and gives us the numbers at the indexes we requested, as shown below:
# taken from challenge sourcefrom ast import literal_evalidxs =literal_eval(input(">>> "))iflen(idxs)>8:print("Ha thats funny")exit()for idx inrange(624): rand_out = random.getrandbits(32)if idx in idxs:print(rand_out)
The idea is to somehow crack the random seed with access to only 8 generated numbers. This task is trivial if we had access to all 624 generated numbers, thanks to randcrack. However, having 8 numbers makes it a lot harder.
Here's where the cheese comes in. The server code reads the seed from a text file; see below:
from os import urandom# check if seed.txt existstry: seed =open("seed.txt","rb").read()except: seed =urandom(8)open("seed.txt","wb").write(seed)
If the seed is the same every time, we can just enter indices 0, 1, ... 7 on our first run, then 8, 9, ... 15 on our second run, and so on, where we enter indices 8n, 8n+1, ... 8n+7 on the nth run. We do this until we get all 624 numbers, at which point we use randcrack.
However, I didn't see this, and found the actual solution.
Doing a bit of searching leads us to an interesting blog on breaking Python's PRNG. The bottom of this blog contains a link to a GitHub repository with example seed recovery code.
Using the seed recovery code provided and then decrypting the AES, we are able to get our flag.