This is a challenge from Cryptography section of Standcon 2021. The challenge wants us to break the PRNG system.
Statement
So you think you are the best mind reader in the entire galaxy? We shall see about that…
#!/usr/bin/env python
importrandomfromstringimportascii_lowercaselife=3defprintFlag():print("[REDACTED]")exit(0)defbanner():print(" ╔══════════════════════════════════════════════════════╗")print(" ║ ║")print(" ║ ███╗ ███╗██╗███╗ ██╗██████╗ ║")print(" ║ ████╗ ████║██║████╗ ██║██╔══██╗ ║")print(" ║ ██╔████╔██║██║██╔██╗ ██║██║ ██║ ║")print(" ║ ██║╚██╔╝██║██║██║╚██╗██║██║ ██║ ║")print(" ║ ██║ ╚═╝ ██║██║██║ ╚████║██████╔╝ ║")print(" ║ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝ ║")print(" ║ ║")print(" ║ ██████╗ ███████╗ █████╗ ██████╗ ███████╗██████╗ ║")print(" ║ ██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔════╝██╔══██╗ ║")print(" ║ ██████╔╝█████╗ ███████║██║ ██║█████╗ ██████╔╝ ║")print(" ║ ██╔══██╗██╔══╝ ██╔══██║██║ ██║██╔══╝ ██╔══██╗ ║")print(" ║ ██║ ██║███████╗██║ ██║██████╔╝███████╗██║ ██║ ║")print(" ║ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝ ║")print(" ║ ║")print(" ╚══════════════════════════════════════════════════════╝")defprintMenu():print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=")print("1. Play game")print("2. View Top players")print("3. Exit")print("Please select your option")choice=input("> ")print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=")returnchoicedefreadFile():firstName=open('./first-names.txt','r')lastName=open('./last-names.txt','r')game=open('./game.txt','r')fnArr=firstName.read().splitlines()lnArr=lastName.read().splitlines()gameArr=game.read().splitlines()returnfnArr,lnArr,gameArrdefgen(initSeed,fnArr,lnArr,gameArr):genNameArr=[]genHighscoreArr=[]toPredictArr=[]seed=[]r=random.Random()r.seed(initSeed)forcountinrange(730):x=r.getrandbits(32)seed.append(x)if(count<350):fnIndex=x&0xFFFFlnIndex=x>>16Name=" ".join([fnArr[fnIndex],lnArr[lnIndex]])genNameArr.append(Name)if(count>=350andcount<700):genHighscoreArr.append(x)elif(count>=700):toPredictArr.append(gameArr[x&0xFFFF])returngenNameArr,genHighscoreArr,toPredictArrdefhangman(tried_letters):print("LIFE: {0}".format(life))iflen(tried_letters)>=1:print("Used letters: {}".format(", ".join(lettersforlettersintried_letters)))defprint_error(why):print(why)defgameLogic(letters,random_word):globallifetried_letters=[]whileTrue:try:hangman(tried_letters)forletterinletters:print(letter,end=' ')letter_input=str(input("\nLetter: ")).lower()ifletter_input==random_word:print('\nThe word is {0}. You win!'.format(random_word))returniflen(letter_input)>1orletter_inputnotinascii_lowercase:print_error("Invalid Input\nOnly single letters")continueifletter_inputintried_letters:print_error("You already tried this letter!")continueelse:tried_letters.append(letter_input)ifletter_inputinrandom_word:letter_index=[indexforindex,valueinenumerate(random_word)ifvalue==letter_input]iflen(letter_index)>1:foriinletter_index:letters[i]=letter_inputelifletter_inputinletters:print_error("You already tried this letter!")continueelse:letters[letter_index[0]]=letter_inputelse:life-=1if''.join(letters)==random_word:hangman(tried_letters)forletterinletters:print(letter,end=' ')print('\nThe word is {0}. You win!'.format(random_word))returneliflife==0:hangman(tried_letters)print('\nThe word is {0}'.format(random_word))print("The Hangman was hanged. You loose!")exit(0)exceptIndexError:print_error("Invalid Input\nOnly single Letters")continuedefplayGame(toPredictArr):print("So you think you are a mind reader huh!?")print("Try to guess what i am thinking 30 times in a row and win the grand prize")print("I am even going to give u 3 chances, not like that would help >:)")foridx,wordsinenumerate(toPredictArr):letters=[]for_inrange(len(words)):letters.append("_")print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")print("Round "+str(idx+1))print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")gameLogic(letters,words)ifidx==29:printFlag()defviewTopPlayers(nameArr,highscoreArr):print("Now showing the top 350 players!!!")print("══════════════════════════════════")print('{0:25} {1}'.format("Name","ID"))print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")forxinrange(len(nameArr)):print('{0:25} {1}'.format(nameArr[x],highscoreArr[x]))defmain():initSeed=random.getrandbits(32)fnArr,lnArr,gameArr=readFile()nameArr,highscoreArr,toPredictArr=gen(initSeed,fnArr,lnArr,gameArr)banner()whileTrue:choice=printMenu()if(choice=='1'):playGame(toPredictArr)elif(choice=='2'):viewTopPlayers(nameArr,highscoreArr)elif(choice=='3'):exit()if__name__=="__main__":main()
Observation
After reading the code, we can find out that the server is using Python library random to generate the namelist.
Looking at the python documentation for random. They explicitly state that
Warning : The pseudo-random generators of this module should not be used for security purposes. For security or cryptographic uses, see the secrets module.
Can we exploit the python random library?
Solution
Yes we actually can! There’s also an online library for that!
Internally python uses mersenne twister PRNG.
I did not research on how the algorithm and the exploits work.
Basically the PRNG is not cryptographically secure.