guess_game

点击此处获得更好的阅读体验


WriteUp来源

本WP由Vanish提供

题目考点

  • LCG

  • LFSR

解题思路

LCG攻击原理这里看,NCTF出现过的。

LFSR的攻击手法ctf-wiki上看。

解密流程:

第一步我们通过六个level的值恢复LCG的参数从而预测第七个code,从而达到(500,10)这个level。开局拥有10个coin!

然后我们用9次机会,可以得到72bit的输出,其中前40bit输出逆序可以得到初始的r

但是这里的lfsr其实是39级的,所以至少需要78bit才能恢复

所以我们要爆破6bit的输出

需要说明的是即使有78bit的输出,也有可能因为矩阵没有满秩从而解不出key来。

然后这里我换了个思路,我硬猜,要求前九次猜中一个,这样我就可以多拿两组数据(其实多拿一次就够了)

这样我就能拿到80bit的输出,然后我用下面那个sage脚本,根据wiki所述构造矩阵,解个线性方程,得到key。点解密的时候要放《好运来》,增加解密成功的概率,唱《国歌》应该也可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
output = '10100010010100010010101001100110000000000111101111011101010101000111110011111011'

N = 39
F = GF(2)
ans=[]
out = output
Sn = [vector(F,N) for j in range(N+1)]
for j in range(N+1):
Sn[j] = list(map(int,out[j:j+N]))

X = matrix(F,Sn[:N])
invX = (X^-1)
Y = vector(F,Sn[-1])
Cn = Y * invX
res = ''.join(str(i) for i in Cn)
ans.append(int(res[::-1],2))
print (ans)
print(len(ans))

>> [71834525659]
>> 1

用于交互的脚本

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# -*- coding: utf-8 -*-
from Crypto.Util.number import long_to_bytes,bytes_to_long,inverse
from pwn import *
context.log_level = 'debug'
def gcd(a, b):
while b:
a, b = b, a%b
return a

def crack_unknown_increment(states, modulus, multiplier):
"""
利用伪随机序列s,模数n,乘数m,恢复增数c

states:伪随机数序列s
modulus:模数n,
multiplier:乘数m
increment:增量c
return:根据n,m,s0,s1恢复c,并返回
"""
increment = (states[1] - states[0]*multiplier) % modulus
return modulus, multiplier, increment

def crack_unknown_multiplier(states, modulus):
"""
利用伪随机序列s和模数n,恢复乘数m

states:伪随机数序列s
modulus:模数n,
multiplier:乘数m
return:根据n,s0,s1,s2恢复m,然后利用crack_unknown_increment恢复c
"""
multiplier = (states[2] - states[1]) * inverse(states[1] - states[0], modulus) % modulus
return crack_unknown_increment(states, modulus, multiplier)

def crack_unknown_modulus(states):
"""
利用初值和随后LCG产生的连续的几个值,恢复模数n

modulus:模数n
states:伪随机数序列s
diffs:相邻随机数s的差值:t序列
zeroes:与t系列有关的差值,zeroes = t2*t0 - t1*t1,t0,t1,t2相邻
return:根据n,s0,s1,s2恢复m,然后利用crack_unknown_increment恢复c
"""
diffs = [s1 - s0 for s0, s1 in zip(states, states[1:])]
zeroes = [t2*t0 - t1*t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
modulus = abs(reduce(gcd, zeroes))
return crack_unknown_multiplier(states, modulus)

def lcg(seed,params):
"""
LCG算法

params:(multiplier,increment,modulus)
seed:伪随机序列中第一个值s0
return:返回一个迭代器,其中的值是私钥序列
"""
(m,c,n)=params
x = seed % n
yield int(x)
while True:
x = (m * x + c) % n
yield int(x)

def decode(pri,pub,c,p):
"""
ElGamal解密算法

pri:一方私钥
pub:另一方公钥
sharekey:双方协商密钥
c:密文
p:模数
m:明文
return:返回明文字符
"""
sharekey = pow(pub,pri,p)
m=long_to_bytes(c*inverse(sharekey,p)%p)
return m

class Game:
def __init__(self, n, key, r, b):
self.n = n#40
self.key = key
self.r = r & (2 ** self.n - 1)
self.b = b#8

def out(self):
o = self.r & 1
p = self.r & self.key
q = 0
while p:
q = q + p & 1
p = p >> 1
q = q & 1
t = q << (self.n - 2) & (2 ** self.n - 1)
self.r = t | (self.r >> 1) & (2 ** self.n - 1)
return o

def next(self):
res = 0
for i in range(self.b):
o = self.out()
res |= o << (self.b - 1 - i)
return res

def getr(s):
r = ''
for i in s:
b = bin(i)[2:].rjust(8,'0')
for each in b:
r = each+r
print int(r,2)
return int(r,2)

def getk(s):
k=""
for i in s:
b = bin(i)[2:].rjust(8,'0')
for each in b:
k += each
return k

sh = remote('59.110.243.101','5123')
sh.recvuntil("Your choice:\n")
sh.sendline("2")
sh.recvuntil("Baby:")
Baby = sh.recvuntil("\n")[:-1]
sh.recvuntil("Easy:")
Easy = sh.recvuntil("\n")[:-1]
sh.recvuntil("Normal:")
Normal = sh.recvuntil("\n")[:-1]
sh.recvuntil("Hard:")
Hard = sh.recvuntil("\n")[:-1]
sh.recvuntil("Master:")
Master = sh.recvuntil("\n")[:-1]
sh.recvuntil("Crazy:")
Crazy = sh.recvuntil("\n")[:-1]

pri=[Baby,Easy,Normal,Hard,Master,Crazy]

(n,m,c)=crack_unknown_modulus([int(each) for each in pri])

x=int(pri[0])

key = lcg(x,(m,c,n))
for _ in range(6):
next(key)
choice = next(key)
sh.recvuntil("Input the level code:\n")

sh.sendline(str(choice))
sh.recvuntil("Your choice:\n")
sh.sendline("1")
#flag=""
s=[]
F = 0
for _ in range(9):
sh.recvuntil("Input your answer:\n")
sh.sendline("0")
ans = sh.recvuntil("The answer is ")
if 'Right' in ans:
F = 1
print("NICE")
s.append(int(sh.recvuntil("!")[:-1]))

if F == 1:
sh.recvuntil("Input your answer:\n")
sh.sendline("0")
ans = sh.recvuntil("The answer is ")
s.append(int(sh.recvuntil("!")[:-1]))

r = getr(s[:5])
output = getk(s[:])
print "r:"+str(r)
print "output:"+ output
print s
k = input("k:")
game = Game(40,k,r,8)
for _ in range(10):
next(game)

for _ in range(500):
sh.recvuntil("Input your answer:\n")
sh.sendline(str(next(game)))

sh.interactive()

[DEBUG] Received 0x3f bytes:
'Your coin: 500\n'
'You Win!\n'
'flag{12727f129b72e685388c6a67538ee9b0}\n'