jeudi 4 mai 2017

Predict choice / bounded rand

the task i am given is to win 50 times in a row with a self-written client against this RockPaperScissor-PythonServer

import SocketServer,threading,os,string
import random, time
f = open('secret.txt')
offset = int(f.readline().strip())

choices = {
        'r': 'rock',
        'p': 'paper',
        's': 'scissors'
}

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

class MyTCPHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        rnd = random.Random()
        # Initialize the random number generator to some secret value
        # Note: the value of offset is too big to guess/bruteforce you need to find a better way :)
        rnd.seed(int(time.time() + offset))
        self.request.sendall("Rock paper scissors is back\n")
        win_count = 0
        play_again = True
        while play_again:
            while win_count < 50:
                self.request.sendall("choose one [r] rock, [p] paper, [s] scissors: ")
                your_choice = self.request.recv(1024).strip()
                if not your_choice in 'rps':
                    continue
                self.request.sendall("Your choice %s\n" % choices.get(your_choice))
                my_choice = rnd.choice("rps")
                self.request.sendall("My choice %s\n" % choices.get(my_choice))
                if my_choice == your_choice:
                    self.request.sendall("Its a tie, sorry you need to win 50 times in a row, a tie is simply not good enough.\nWho ever said life was fair?\n")
                    break
                if ((my_choice == 'r' and your_choice == 'p') or 
                        (my_choice == 'p' and your_choice == 's') or 
                        (my_choice == 's' and your_choice == 'r')):
                    win_count += 1
                    self.request.sendall("Arghhh. you beat me %s times\n" % win_count)
                else:
                    self.request.sendall("You loose!\n")
                    break

            if win_count == 50:
                self.request.sendall("50 times in a row?!? are you some kind of mind reader?\n")
                return
            else:
                win_count = 0
                answer = ''
                while answer not in ('y','n'):
                    self.request.sendall("Play again? (y/n): ")
                    answer = self.request.recv(1024).strip().lower()
                    if answer == 'n':
                        return

SocketServer.TCPServer.allow_reuse_address = True
server = ThreadedTCPServer(("0.0.0.0", 1178), MyTCPHandler)
server_thread = threading.Thread(target=server.serve_forever)
server_thread.daemon = True
server_thread.start()
server.serve_forever()

I have read in the python random.py doc and on various sites that the core random number generator that pythons random class uses (MersenneTwister) is not appropriate for security-relevant things because it is predictable when an attacker manages to obtain 624 consecutive numbers.

I have already a client that plays 624 times rock and in each round detects the server choice, converts it to the respective array index in [rps] and writes that number to a file. So in the end theres a long file containing lots of 0's, 1's and 2's like this

0
1
0
2
2
0
....

The most important line in the server code for me is apparently

my_choice = rnd.choice("rps")

which is implemented as (extract from random.py):

def choice(self, seq):
  """Choose a random element from a non-empty sequence."""
  return seq[int(self.random() * len(seq))] # raises IndexError if seq is empty

Here i read that in order to predict next numbers i need to record 624 consecutive numbers and restore the state by reversing / undoing certain transformations, however i think that the direct core rng output, which is a float between [0.0, 1.0), is required for that...

To get the core rng output from the sequence index it seems that i just have to exactly reverse the above code of the function "choice()", which would be something like

seq_value = seq[int(core_rng_out * len(seq))]
seq_index = int(core_rng_out * len(seq))
int^-1(seq_index) = core_rng_out * len(seq)
int^-1(seq_index) / len(seq) = core_rng_out
core_rng_out = int^-1(seq_index) / 3

The above is supposed to be something like resolving a math equation for a certain variable. Divided by 3 because the sequence is 3-sized ("rps"), however what is the inverse of pythons int(...) function?!? Above i tried to abstractly mark it as inverse by making it ^-1.

And furthermore is it even possible to get the rng float at all?!?, because in pythons int-doc it says when int(...) is given a float some truncation will/may happen...?!

Or this maybe a completely wrong approach and i can beat the server in an easier way?




Aucun commentaire:

Enregistrer un commentaire