ACTF Dice Game Writeup

ACTF也算是结束了,像我这样的渣渣只能是挑所有题目中最简单的来了。

这次的ctf好坑啊,竟然所有逆向破解题都是apk,我完全不会啊(>﹏<),只能弄到ppc了。

Dice Game 题目直接把python的源码贴了出来。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#!/usr/bin/env python
# coding: utf-8
"""
Yet another problem for ACTF2015.
By H4sIAOzUQ1UCAwtIzUuvLFVwzkjNU9AoAHMccjKTiksSi4oy0yr1ivM1uQCpNCsJJAAAAA==
COPYLEFT, ALL WRONGS RESERVED.
"""


import random
import time
import sys
assert sys.version_info >= (3, 2)


NUM_ROUNDS = 1000
SCORE_INITIAL = 100
SCORE_ROUND = 10
SCORE_BONUS = 2


class WeBreakup(Exception):
"""Hmmm.. :("""
pass


def play_round(scores):
"""Plays a single round. Returns round status and new scores."""
print('..and your guess is?')
guess = input()
point = random.randint(1, 6)
mapping = {x: 'small' if x <= 3 else 'big' for x in range(1, 7)}
if guess == mapping[point]:
print('Correct. :(')
guessing_correct = True
# You know I'm the boss, right?
score_delta = SCORE_ROUND - SCORE_BONUS
scores = (scores[0] - score_delta, scores[1] + score_delta)
else:
print('Incorrect! :)')
guessing_correct = False
# You know I'm the boss, right?
score_delta = SCORE_ROUND + SCORE_BONUS
scores = (scores[0] + score_delta, scores[1] - score_delta)
return (guessing_correct, scores)


def play_game():
"""Plays a game."""
print('Hey hey, you you, I wanna play a game! :)')
reply = input()
if reply not in ('Sure!', 'Okay!', 'With pleasure. :)'):
raise WeBreakup('You bad guy :(')
scores = tuple(SCORE_INITIAL for i in range(2))
random.seed(int(time.time() * 1e5))
recently_is_beaten = []
print('Well, we play a simple dice game! :)')
for i in range(NUM_ROUNDS):
is_beaten, scores = play_round(scores)
print('After round %d the scores are %s' % (i, scores))
if scores[0] <= 0:
raise WeBreakup('I feel humiliated. :(')
recently_is_beaten.append(is_beaten)
if recently_is_beaten[-5:].count(True) >= 5:
raise WeBreakup('How dare you beat me 5 times in a streak! :(')
if scores[0] < scores[1]:
raise WeBreakup('Do you think it is more important to win this game than making me happy? :(')
elif scores[1] > scores[0]:
raise WeBreakup('You are still too young too simple! :(')
else:
print(u'I \u2661 U!')
response = input()
if response == 'Me too! <3':
key_f = open('./flag', 'r')
print('Flag: %s' % key_f.read().strip())
key_f.close()


if __name__ == '__main__':
play_game()

确实算是个掷筛子的游戏,向服务器发送“big”或者“small”猜大小。双方各100分,猜对我方加分8分,猜错扣12分,对方反之。猜1000次。如果最后两人的分数一样,则打印flag。一个两人零和的游戏。但是猜对概率是二分之一。而且还有2条限制条件
1.不能连赢5次
2.对方分数不能小于0
有这么多限制,在概率上达到平局的概率基本上是不可能了。虽然可以发送些“aaa”之类的可以保正出错。但是无论如何,我方是必输的。。。╮( ̄▽ ̄)╭

所以只能是另外想办法了。

然后注意到了这个random.seed(int(time.time() 1e5))这个随机函数是以系统时间来做seed的。相信了解过随机函数的都知道,只要知道了一个随机函数的seed和函数的算法,那么随机函数的值是完全可以预测的。问题就是得到int(time.time() 1e5)的值了。不过因为网络延时和系统时间的不同,再加上坑爹的1e5次。所以要同步时间是个大问题。

所以先写了这么两段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

import socket
import time
import random
list = []
scores = 0
a=0
str=""
s = socket.socket()
s.connect(('119.254.101.232',9999))

print (s.recv(100))
s.send(b'Sure!\n')
print (s.recv(100))

Seed=int(time.time() * 1e5)
i=0
right = 0
while i<20:
s.send(b"big\n")
h=s.recv(100)
print (h)
if h[0] == 67:
list = list + [1]
scores += 1
else:
list = list + [0]
i+=1
print (v,list)
s.close()

第一段代码,用本地的一个time函数获取seed可能的值,然后从服务器上拖了20次猜数字的结果下来。这里我故意在recv之后再调用time。目的是为了让本地的时间数字上大于服务器端的。这样算法比较简单。

把刚才的Seed,和list粘贴到这里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
flag=0
x=1
while(flag==0):
x += 1
random.seed(v-x)
print (x)
i=0
while(i<20):
point = random.randint(1, 6)
if point<=3:
if list[i] == 1:
break
else:
if list[i] == 0:
break
i += 1
else:
print ("boom",x)
flag = 1

这段代码模拟了服务器上的环境,用暴力枚举的方法把seed给爆破了出来。大概是5W左右。。。
坑得的1e5。每次同步修正的数值都不一样,波动都在100以上。在这里卡住了。

后来行为来回的粘贴挺麻烦的,所以我把两段代码给放在一起,然后再得到样本的同时直接跑出修正值来看看。

然后,我为我的智商感到担忧。。。既然可以直接跑出seed的值了。我干啥还要断开连接呢?直接继续跑就可以了(╯‵□′)╯︵┻━┻

然后,就有了下面这段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/bin/env python
#coding:utf-8

import socket
import time
import random
list = []
right_times = 0
a=0
str=""
s = socket.socket()
s.connect(('119.254.101.232',9999))

print (s.recv(100))
s.send(b'Sure!\n')
print (s.recv(100))

x=50000
v=int(time.time() * 1e5-x)
random.seed(v)
i=0
right = 0
while i<20:
point = random.randint(1, 6)
s.send(b"big\n")
h=s.recv(100)
print (h)
if h[0] == 67:
list = list + [1]
right_times += 1
else:
list = list + [0]
i+=1


print(v)
flag=0
x=1
while(flag==0):
x += 1
random.seed(v-x)
print (x)
i=0
while(i<20):
point = random.randint(1, 6)
if point<=3:
if list[i] == 1:
break
else:
if list[i] == 0:
break
i += 1
else:
print ("boom",x)
flag = 1

v -=x
random.seed(v)
i=0
while i<20:
point = random.randint(1, 6)
i+=1

i=0
k= right_times//4+1
while i<230+k+right_times:
point = random.randint(1, 6)
s.send(b"aaa\n")
h=s.recv(100)
print (h)
i+=1
q=0
while right_times<600:
if q ==4:
point = random.randint(1, 6)
s.send(b"aaa\n")
h=s.recv(100)
print (h)
q=0

point = random.randint(1, 6)
if point > 3:
s.send(b"big\n")
else:
s.send(b"small\n")
h=s.recv(100)
print (h)
q += 1
right_times +=1
s.send(b"Me too! <3\n")
h=s.recv(100)
print (h)

s.close()

总之,思想就是先在线取样本,在本地跑seed,然后总共构造出赢600次,输400次。注意不要半路被踢和random次数始终和服务器保持一致就可以了。最后就是服务器有时间限制,如果超时直接断开连接。所以注意跑seed的时候要稍微估算一下防止超时。

PS:靠着我那刚学1星期的python写出的半吊子代码,就不要吐槽了 ╮( ̄▽ ̄)╭
祝贺协会在这次ctf中取得好成绩。