<< back
15 March 2021
Dice Roll
Challenge Source: NahamCon CTF 2021Challenge Category: cryptography
Challenge Text
Author: @JohnHammond#6971
When you have just one source of randomness, it's "a die", but when you can have muliple -- it's 'dice!'
NOTE: You are welcome to "brute force" this challenge if you feel you need to. ;)
Download the file below and press the Start button on the top-right to begin this challenge.
Solution
- Downloading the code, we can see that the program allows us to randomly generate a 32 bit integer that represents a dice roll, reset the random seed (“shake the dice”), or try to guess the outcome of the next dice roll.
#!/usr/bin/env python3
import random
import os
banner = """
_______
______ | . . |\\
/ /\\ | . |.\\
/ ' / \\ | . . |.'|
/_____/. . \\ |_______|.'|
\\ . . \\ / \\ ' . \\'|
\\ . . \\ / \\____'__\\|
\\_____\\/
D I C E R O L L
"""
menu = """
0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)
"""
dice_bits = 32
flag = open('flag.txt').read()
print(banner)
while 1:
print(menu)
try:
entered = int(input('> '))
except ValueError:
print("ERROR: Please select a menu option")
continue
if entered not in [0, 1, 2, 3]:
print("ERROR: Please select a menu option")
continue
if entered == 0:
print("Our dice are loaded with a whopping 32 bits of randomness!")
continue
if entered == 1:
print("Shaking all the dice...")
random.seed(os.urandom(dice_bits))
continue
if entered == 2:
print("Rolling the dice... the sum was:")
print(random.getrandbits(dice_bits))
continue
if entered == 3:
print("Guess the dice roll to win a flag! What will the sum total be?")
try:
guess = int(input('> '))
except ValueError:
print("ERROR: Please enter a valid number!")
continue
total = random.getrandbits(dice_bits)
if guess == total:
print("HOLY COW! YOU GUESSED IT RIGHT! Congratulations! Here is your flag:")
print(flag)
else:
print("No, sorry, that was not correct... the sum total was:")
print(total)
continue
-
Since seeding is optional and getrandbits (which is a deterministic, pseudo-random generator) is used, we should be able to predict values. Luckily, there is a tool called randcrack available that can use 32-bit getrandbits integer inputs to predict newly generated numbers with high accuracy. It takes 624 integers for randcrack to be ready to predict new numbers.
-
To that end, we can build a script to interact with the challenge connection, roll the dice via option #2 and feed the results into randcrack. After this we just use randcrack to guess the next roll with option #3 and get the flag.
import os
import sys
import struct
import socket
from randcrack import RandCrack
rc = RandCrack()
address = 'challenge.nahamcon.com'
port = 31699
buffer = 4096
tcpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpsocket.connect((address, port))
# Get the initial headers
received_data = tcpsocket.recv(buffer).decode('utf-8')
print ('Received:', received_data)
# Check information
tcpsocket.send(b'0\n')
received_data = tcpsocket.recv(buffer).decode('utf-8')
print ('Received:', received_data)
# Shake the dice
tcpsocket.send(b'1\n')
received_data = tcpsocket.recv(buffer).decode('utf-8')
print ('Received:', received_data)
# Roll the dice enough for RandCrack to be able to work its magic
for i in range(624):
tcpsocket.send(b'2\n')
received_data = tcpsocket.recv(buffer).decode('utf-8')
received_data_lines = received_data.splitlines()
received_roll = int(received_data_lines[1])
rc.submit(received_roll)
#print ('Received Roll Number',i,':', received_roll)
# Get a prediction of the next roll from RandCrack
try:
roll_prediction = rc.predict_randrange(0, 4294967294)
print ("Roll Prediction: ", roll_prediction)
except:
print ('Not enough rolls to predict - change range to 625.')
exit()
# Try to guess the next roll and output the response
tcpsocket.send(b'3\n')
received_data = tcpsocket.recv(buffer).decode('utf-8')
print ('Received:', received_data)
roll_prediction_encoded = (str(roll_prediction) + '\n').encode('utf-8')
tcpsocket.send(roll_prediction_encoded)
received_data = tcpsocket.recv(buffer).decode('utf-8')
print ('Received:', received_data)
tcpsocket.close()
─(shinris3n㉿kodachi)-[~/sec/CTFs/Nahamcon_2021/dice_roll]
└─$ python3 roll_a_lot.py
Received:
_______
______ | . . |\
/ /\ | . |.\
/ ' / \ | . . |.'|
/_____/. . \ |_______|.'|
\ . . \ / \ ' . \'|
\ . . \ / \____'__\|
\_____\/
D I C E R O L L
0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)
>
Received: Our dice are loaded with a whopping 32 bits of randomness!
0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)
>
Received: Shaking all the dice...
0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)
>
Roll Prediction: 131726453
Received: Guess the dice roll to win a flag! What will the sum total be?
>
Received: HOLY COW! YOU GUESSED IT RIGHT! Congratulations! Here is your flag:
flag{e915b62b2195d76bfddaac0160ed3194}
0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)