一道很有意思的 lfsr 题。给了一个 task.py
如下:
import hashlib
from secret import KEY,FLAG,MASK
assert(FLAG=="de1ctf{"+hashlib.sha256(hex(KEY)[2:].rstrip('L')).hexdigest()+"}")
assert(FLAG[7:11]=='1224')
LENGTH = 256
assert(KEY.bit_length()==LENGTH)
assert(MASK.bit_length()==LENGTH)
def pad(m):
pad_length = 8 - len(m)
return pad_length*'0'+m
class lfsr():
def __init__(self, init, mask, length):
self.init = init
self.mask = mask
self.lengthmask = 2**(length+1)-1
def next(self):
nextdata = (self.init << 1) & self.lengthmask
i = self.init & self.mask & self.lengthmask
output = 0
while i != 0:
output ^= (i & 1)
i = i >> 1
nextdata ^= output
self.init = nextdata
return output
if __name__=="__main__":
l = lfsr(KEY,MASK,LENGTH)
r = ''
for i in range(63):
b = 0
for j in range(8):
b = (b<<1)+l.next()
r += pad(bin(b)[2:])
with open('output','w') as f:
f.write(r)
另外给了一条信息:
001010010111101000001101101111010000001111011001101111011000100001100011111000010001100101110110011000001100111010111110000000111011000110111110001110111000010100110010011111100011010111101101101001110000010111011110010110010011101101010010100101011111011001111010000000001011000011000100000101111010001100000011010011010111001010010101101000110011001110111010000011010101111011110100011110011010000001100100101000010110100100100011001000101010001100000010000100111001110110101000000101011100000001100010
分析程序,是一个很普通的 lfsr,但是我们初始状态和掩码都不知道,需要利用出题人给定的 504 个 bit 的信息,推出这所需的 MASK、KEY 一共 512 bit 的信息。
先考虑如何推出掩码。由于 lsfr 的性质,每一次生成的 bit 都会加到向量的最低位,同时丢弃掉最高位那个 bit. 于是在连续 256 次生成之后,原有的 KEY 所有的位都被丢弃,lfsr 的状态会转为我们已知的 256 个 bit —— 也就是题目所给出的串的前 256 位。从此之后,我们完全知道了 lfsr 的状态,只需要在已知状态的情况下推出掩码了。
每连续 256 个 bit 可以生成下一个 bit. 我们知道这 256 个 bit,也知道下一个 bit,但我们不知道掩码。目前面临的问题与下面的任务等价:在 $GF(2)$ 上,256 位的已知的状态向量,点乘上 256 位的掩码向量,得到的数已知。现在求掩码向量。
上面是一个方程;而状态向量有 256 维,我们需要 256 组方程才能解出整个掩码向量。但我们现在只有 504 - 256 = 248 个方程可用,显然秩是不够用的。容易想到,直接猜测 lfsr 此后生成的 8 个 bit,于是就有 256 组方程了;当然求出的答案只有一条是正确的,我们最后会用题目脚本给出的 sha256 条件逐一检验。
解方程组的问题可以转化为矩阵求逆的问题。把 lfsr 的状态一行一行地写在矩阵上,形成的矩阵记为 $M$. 把 lsfr 每次所生成的结果也拼成一个向量,记为 $T$. 那么掩码向量 $v$ 使得:$$M \cdot v = T$$
于是两边左乘 $M ^ {-1}$,可以得到掩码向量:$$v = M ^ {-1} \cdot T$$
爆破掩码的脚本如下:
def test(padding):
s = [int(x) for x in '001010010111101000001101101111010000001111011001101111011000100001100011111000010001100101110110011000001100111010111110000000111011000110111110001110111000010100110010011111100011010111101101101001110000010111011110010110010011101101010010100101011111011001111010000000001011000011000100000101111010001100000011010011010111001010010101101000110011001110111010000011010101111011110100011110011010000001100100101000010110100100100011001000101010001100000010000100111001110110101000000101011100000001100010'] + padding
M = matrix(GF(2), 256, 256)
T = vector(GF(2), 256)
for i in range(len(s) - 256):
M[i] = s[i : i + 256]
T[i] = s[i+256]
try:
mask = M.inverse() * T
print(hex(int(''.join(map(str, (mask))), base=2)))
except:
return
for x in itertools.product([0, 1], repeat = 8):
test(list(x))
接下来考虑求出初始状态 KEY. 我们目前有的东西是:(猜测的)连续 512 个 lfsr 输出,以及与之对应的掩码。注意到第 256 个输出,是由 KEY 的末位,拼接上前 255 个输出所形成的;第 255 个输出,是由 KEY 的倒数两位,拼接上前 254 个输出所形成的。我们可以先求出 KEY 的末位,再求出倒数第二位……以此类推,整个 KEY 就求出来了。
完整的代码如下:
import itertools, hashlib, numpy as np
def int2bin(a, n):
assert 0<=n and a < 2**n
res = np.zeros(n, dtype = int)
for x in range(n):
res[n-x-1] = a % 2
a = a // 2
return res.tolist()
def bin2int(a):
return reduce(lambda x,y: x*2+y, a)
def bitAnd(a, b):
assert len(a) == len(b)
return list(map(lambda x,y: int(x)&int(y), a, b))
def test(padding):
s = [int(x) for x in '001010010111101000001101101111010000001111011001101111011000100001100011111000010001100101110110011000001100111010111110000000111011000110111110001110111000010100110010011111100011010111101101101001110000010111011110010110010011101101010010100101011111011001111010000000001011000011000100000101111010001100000011010011010111001010010101101000110011001110111010000011010101111011110100011110011010000001100100101000010110100100100011001000101010001100000010000100111001110110101000000101011100000001100010'] + padding
M = matrix(GF(2), 256, 256)
T = vector(GF(2), 256)
for i in range(len(s) - 256):
M[i] = s[i : i + 256]
T[i] = s[i+256]
try:
mask = M.inverse() * T
except:
return
suf = []
for i in range(256):
if bitAnd([0] + suf + s[0:255 - i], mask).count(1) % 2 == s[255 - i]:
suf = [0] + suf
else:
suf = [1] + suf
key = hex(bin2int(suf))[2:]
sha = hashlib.sha256(key.encode()).hexdigest()
if sha[:4] == '1224':
print('de1ctf{' + sha + '}')
for x in itertools.product([0, 1], repeat = 8):
test(list(x))
于是得到了 flag: de1ctf{1224473d5e349dbf2946353444d727d8fa91da3275ed3ac0dedeb7e6a9ad8619}