CTF Challenge – Twisted Robot


In my first post I mentioned that I love doing Capture-the-Flag (CTF) challenges, so here is a writeup of a challenge I completed recently. This challenge is part of the Google CTF – Beginners Quest, which is a great place to start if you are new to CTFs. In this post I walk through the steps I took to solve the challenge and at the end I include the python file I wrote to find the secret flag.

This challenge prompts you to download a zip file, which when unzipped contains three files:

  • robo_numbers_list.txt — A text file containing 624 phone numbers
  • RoboCaller1337.py — A python file with several functions (shown below)
  • secret.enc — An encrypted file

Let’s take a look at the python file to see what we can figure out:

 1 import random
 2 
 3 
 4 # Gots to get that formatting right when send it to our call center
 5 def formatNumber(n):
 6     n = str(n)
 7     return f'{n[:3]}-{n[3:6]}-{n[6:]}'
 8 
 9 
10 # This generates random phone numbers because it's easy to find a lot of
11 # people! Our number generator is not great so we had to hack it a bit to
12 # make sure we can reach folks in Philly (area code 215)
13 def generateRandomNumbers():
14     arr = []
15     for i in range(624):
16         arr.append(formatNumber(random.getrandbits(32) + (1 << 31)))
17     return arr
18 
19 
20 def encodeSecret(s):
21     key = [random.getrandbits(8) for i in range(len(s))]
22     return bytes([a ^ b for a, b in zip(key, list(s.encode()))])
23 
24 
25 def menu():
26     print("""\n\nWelcome to the RoboCaller!! What would you like to do?
27 1: generate a new list of numbers
28 2: encrypt a super secret (in beta)
29 3: decrypt a super secret (coming soon!!)
30 4: exit""")
31     choice = ''
32     while choice not in ['1', '2', '3']:
33         choice = input('>')
34         if choice == '1':
35             open('robo_numbers_list.txt', 'w').write(
36                 '\n'.join(generateRandomNumbers()))
37             print("...done! list saved under 'robo_numbers_list.txt'")
38         elif choice == '2':
39             secret = input(
40                 'give me your secret and I\'ll save it as "secret.enc"')
41             open('secret.enc', 'wb').write(encodeSecret(secret))
42         elif choice == '3':
43             print("stay tuned for this awesome feature\n\n")
44         elif choice == '4':
45             print("Thank you for using RoboCaller1337!")
46     return
47 
48 
49 def main():
50     while True:
51         menu()
52 
53 
54 if __name__ == "__main__":
55     main()

The two functions that we should focus on are “generateRandomNumbers()” and “encodeSecret()”. The first was used to generate the list of phone numbers in the provided text file and the second was used to create the encrypted file. Presumably, this encrypted file contains the flag so our goal is to figure out how to undo the encryption process.

The flag was encrypted through the following process:

  1. Breaking it into individual letters
  2. Encoding each letter with UTF-8 encoding, and
  3. XOR-ing each letter with a randomly generated value

In order to retrieve the flag we must reverse this process, however this means we need to figure out the values that were randomly generated for the XOR-ing step. So I turned to Google to see if there exists a way to discover the seed used for “random()”.

My searching informed me that “random()” uses the Mersenne Twister as its pseudo-random number generator (PRNG). And more importantly, it has a vulnerability.

If you are able to acquire 624 consecutive values from “random()”, then you can correctly predict every additional value that it will generate. Fortunately in the provided python program, “random()” is called 624 times to generate the list of phone numbers, which we are given in “robo_numbers_list.txt”.

After some searching I found a module that can be installed and used to predict the future values generated by “random()”. I installed and imported the module, fed in the random numbers from “robo_numbers_list.txt”, XOR-ed these values with each value in “secret.enc”, and undid the UTF-8 encoding of each letter. After combining the letters into a single string and printing it, I was able to see the encrypted message:

CTF{n3v3r_3ver_ev3r_use_r4nd0m}

Here’s the python program I wrote to uncover this flag:

 1 from mt19937predictor import MT19937Predictor
 2 # https://github.com/kmyk/mersenne-twister-predictor
 3 
 4 predictor = MT19937Predictor()
 5 
 6 # Open the phone number list to retrieve the randomly generated numbers
 7 roboNums = []
 8 with open("robo_numbers_list.txt") as infile:
 9     for file in infile:
10         roboNums.append(int(file.replace("-","").strip("\n")) - (1 << 31))
11 
12 # Feed "random" phone numbers into the predictor
13 for num in roboNums:
14     predictor.setrandbits(num, 32)
15 
16 # Retrieve the encoded secret message
17 with open("secret.enc", "rb") as secret:
18     letters = list(secret.readline())
19 
20 # Decrypt each letter in the message by XOR-ing it with the "randomly"
21 # generated value used to encrypt it
22 answer = ""
23 for i in range(len(letters)):
24     answer = answer + chr(letters[i] ^ predictor.getrandbits(8))
25 
26 print(answer)

Print Friendly, PDF & Email

Leave a Reply

Your email address will not be published. Required fields are marked *