预测选择/有界兰特

Predict choice / bounded rand

给我的任务是用自己写的客户端对抗这个RockPaperScissor-PythonServer连胜50次

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()

我在 python random.py 文档和各种网站上读到 pythons random class 使用的核心随机数生成器 (MersenneTwister) 不是适用于与安全相关的事情,因为当攻击者设法获得 624 个连续数字时,这是可以预测的。

我已经有一个播放 624 次摇滚的客户端,并且在每一轮中检测服务器选择,将其转换为 [rps] 中的相应数组索引并将该数字写入文件。所以最后有一个包含很多 0、1 和 2 的长文件,就像这样

0
1
0
2
2
0
....

服务器代码中对我来说最重要的一行显然是

my_choice = rnd.choice("rps")

实现为(摘自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 我读到为了预测下一个数字我需要记录 624 个连续数字并通过反转/撤消某些转换来恢复状态,但是我认为直接核心 rng 输出,这是一个在 [0.0, 1.0) 之间浮动是必需的...

为了从序列索引中获取核心 rng 输出,我似乎只需要完全反转函数 "choice()" 的上述代码,就像

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

以上应该是解决某个变量的数学方程式之类的事情。除以 3 因为序列是 3 大小的 ("rps"), 但是 pythons int(...) 函数的反函数是什么?!?上面我试图通过使它成为 ^-1.

来抽象地将它标记为逆

而且甚至有可能获得 rng 浮点数吗?!?,因为在 pythons int-doc 中它说当 int(...) 被赋予一个浮点数时一些截断 will/may 发生...?!

或者这可能是一个完全错误的方法,我可以用更简单的方法击败服务器?

在我看来,您可以通过同时启动两个连接(在同一秒内)来欺骗服务器。

如果它们在同一秒启动,则随机种子将相同(由于这一行:rnd.seed(int(time.time() + offset))。 因此,服务器将为两个客户端生成相同的选择。

所以需要做的就是让一个客户采用任何(可能会失败的)策略,记录计算机的前 50 个选择。然后,知道了这些,您就可以在另一个连接上玩完全正确的获胜动作。