3

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?

1 Answers1

1

It seems to me that you can trick the server by initiating two connections simultaneously (during the same second).

If they are initiated during the same second, the random seed will be the same (due to this line: rnd.seed(int(time.time() + offset)). Thus, the server will generate the same choices for the two clients.

So what needs to be done is simply have one client go with any (probably losing) strategy, recording the computer's first 50 choices. Then, knowing those, you can play on the other connection exactly the winning moves.

danbanica
  • 3,018
  • 9
  • 22
  • Wow thanks! Haven't tried it yet but that definitely sounds plausible and has to be it... – Pierre_LaCroix May 06 '17 at 14:50
  • "python client_auto.py && python client_interactive.py" starts them after each other right? How can i start them simultaneously? – Pierre_LaCroix May 06 '17 at 15:29
  • i think that && is not appropriate, because it waits for the first command to finish. You can instead use the single &, which runs the first command in background (but any printed data should still show), releasing the terminal and allowing instant execution of the second command. Please let me know if it works! :) – danbanica May 07 '17 at 10:59