CryptoScriptingMiscellaneous

10 months ago - 106 views

5 - Twisted robot

I had some experience in the idea of this challenge, so this was a bit easier than some of the other challenges. Still, though, this challenge had some interesting bits and certainly was very good for understanding random numbers and encryption.

The Challenge

In this challenge, we get a python script and an input and output file of the script.
The script is called RoboCaller1337.py and gives us a menu for interacting with the program.

Python

import random

# Gots to get that formatting right when send it to our call center
def formatNumber(n):
    n = str(n)
    return f'{n[:3]}-{n[3:6]}-{n[6:]}'

# This generates random phone numbers because it's easy to find a lot of people!
# Our number generator is not great so we had to hack it a bit to make sure we can
# reach folks in Philly (area code 215)
def generateRandomNumbers():
    arr = []
    for i in range(624):
        arr.append(formatNumber(random.getrandbits(32) + (1<<31)))
    return arr

def encodeSecret(s):
    key = [random.getrandbits(8) for i in range(len(s))]
    return bytes([a^b for a,b in zip(key,list(s.encode()))])

def menu():
    print("""\n\nWelcome to the RoboCaller!! What would you like to do?
1: generate a new list of numbers
2: encrypt a super secret (in beta)
3: decrypt a super secret (coming soon!!)
4: exit""")
    choice = ''
    while choice not in ['1','2','3']:
        choice = input('>')
        if choice == '1':
            open('robo_numbers_list.txt','w').write('\n'.join(generateRandomNumbers()))
            print("...done! list saved under 'robo_numbers_list.txt'")
        elif choice == '2':
            secret = input('give me your secret and I\'ll save it as "secret.enc"')
            open('secret.enc','wb').write(encodeSecret(secret))
        elif choice == '3':
            print("stay tuned for this awesome feature\n\n")
        elif choice == '4':
            print("Thank you for using RoboCaller1337!")
    return

def main():
    while True:
        menu()

if __name__ == "__main__":
    main()

Looking through the code there is the functionality to generate a lot of random phone numbers (option 1). The numbers it generates are saved in robo_numbers_list.txt and are also included in the download.
It also has the ability to encrypt a secret (option 2) and save the output in secret.enc, another file that was included.

Analysing

There were two things I noticed. The encryption option takes an argument and xor's it with some random bits generated with the Python random module. This module is often used to generate random numbers but is not very safe when it comes to cryptography. Because using a lot of random values with the same seed, you can crack the seed and predict future values. The second thing was the fact that in the robo_numbers_list.txt there are in fact a lot of random numbers generated with the same seed. And there are 624 of those numbers, exactly the same about of numbers that the RandCrack module needs to crack the seed.
This gave me a pretty good suspicion that we need to crack the seed using RandCrack, and then predict the value that was used to encrypt the secret.

Solution

To use RandCrack we need to give it 624 values that were generated with one of the random functions. But the robo_numbers_list.txt is still in phone number format. Looking at the function that generates it, it just adds 2^31 to the random 32-bit number, and then puts - characters every 3 digits to get a format similar to a phone number. We can decode these numbers to their original value by removing the dashes and subtracting 2^31.
Then after submitting all these values to RandCrack, we can predict the next value. The XOR operation is symmetric, meaning we can just encrypt it again with the same random value to get back the original secret. It generates a random 8-bit value for every character, so we just need to predict an 8-bit value for every character.

When we combine all this into a script we get the following:

Python

from randcrack import RandCrack

rc = RandCrack()

def decodeRandomNumber(number):
    return int(number.replace("-", "")) - (1<<31)

def decodeSecret(s):
    # Predict random key using RandCrack
    key = [rc.predict_getrandbits(8) for i in range(len(s))]

    # XOR key and secret
    return bytes([a^b for a,b in zip(list(s),key)]).decode('utf-8')

# Read all lines from robo_numbers_list.txt
with open("robo_numbers_list.txt") as f:
    for line in f.read().splitlines():
        # Decode phone number and submit it to RandCrack
        rc.submit(decodeRandomNumber(line))

# Decrypt the secret by predicting the random values used
with open("secret.enc", "rb") as f:
    print(decodeSecret(f.read()))

This will crack the seed and predict the random value, then decrypt the secret to print the flag!
CTF{n3v3r_3ver_ev3r_use_r4nd0m}