Tags: pwntools rng 

Rating:

# Dice Roll
**Category: Cryptography**

This challenge lets us connect to a server to play a dice roll guessing application:

```
$ nc challenge.nahamcon.com 30260

_______
______ | . . |\
/ /\ | . |.\
/ ' / \ | . . |.'|
/_____/. . \ |_______|.'|
\ . . \ / \ ' . \'|
\ . . \ / \____'__\|
\_____\/

D I C E R O L L

0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)

> 2
Rolling the dice... the sum was:
509869294

0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)

> 3
Guess the dice roll to win a flag! What will the sum total be?
>
```

Looks like the program generates random numbers, and we need to correctly predict the upcoming number to win the flag.

The source code was provided:

```python
#!/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
```

The key takeaway is that we are using `random.getrandbits(32)` to generate the random numbers. Python's pseudonumber generator uses the Mersenne Twister under the hood. We can use RandCrack to take numbers from the dice program as input and predict the next value. From the RandCrack docs:

> This cracker works as the following way. It obtains first 624 32 bit numbers from the generator and obtains the most likely state of Mersenne Twister matrix, which is the internal state. From this point generator should be synchronized with the cracker.

I wrote this program to take in the required 624 integers and predict the next one:

```python
#!/usr/bin/env python3

from progress.bar import Bar
from pwn import *
from randcrack import RandCrack

REQUIRED_INTEGER_COUNT = 624

rc = RandCrack()
io = remote('challenge.nahamcon.com', 30260)

with Bar('Progress', max=REQUIRED_INTEGER_COUNT) as progress_bar:
for i in range(REQUIRED_INTEGER_COUNT):
io.sendlineafter('> ', '2') # Roll the dice
io.recvline()
number = int(io.recvline())
rc.submit(number)
progress_bar.next()

io.sendlineafter('> ', '3') # Start a prediction
prediction = rc.predict_getrandbits(32)
io.sendlineafter('> ', str(prediction))
io.interactive()
```

Output:
```
$ ./exploit.py
[+] Opening connection to challenge.nahamcon.com on port 30260: Done
Progress |################################| 624/624
[*] Switching to interactive mode
HOLY COW! YOU GUESSED IT RIGHT! Congratulations! Here is your flag:
flag{e915b62b2195d76bfddaac0160ed3194}
```

Original writeup (https://github.com/ryan-cd/ctf/tree/master/2021/nahamcon/cryptography/dice_roll).