BUUCTF 每日打卡 2021-7-24
引言
台风天,还练了一天车,淦
[SUCTF2019]MT
来填坑了 首先看加密代码:
1 | from Crypto.Util import number |
看到这个convert
函数,我就有些胆怯了 这个位运算看起来完全打乱了加密内容(而且之前碰到类似的题目都没做出来) 尤其是第二三步,感觉完全乱了 看了一眼别人的wp,受到了启发 发现bin(2029229568)
后9位都是0,而bin(2245263360)
后17位(其实是18位)都是0,与移位的位数相同,那就好办了 首先要明确一件事:位运算符的优先级 移位(>>和<<) > 与运算(&) > 异或运算(^) 就以'abcd'为例,推出第四和第三步的解密过程(第一和第二步同理) 因为我没有装python2的环境,就用python3把加密代码重写了一遍,做了一点小修改 代码如下:
1 | from Crypto.Util.number import * |
第四步操作
首先要知道,">>"是二进制位向右移动,低位丢弃,高位补0 经过前三步加密的二进制结果为(当然,解密时以下结果是未知的): 10100101011101011110111001110111记为m4 那么 m4>>19 的结果为 0000000000000000000101001010111 01011110111001110111 高位补0<- ->低位丢弃 接下来是异或运算 解密需要用到异或运算的两个性质: 1、若a=b ^ c,则c=a ^ b 2、a=a ^ 0 1
2
3
4
5
6
7
8
9
10
11
12按位加密的过程中,m4的前19位都是和0做异或运算,也就是说加密结果cipher的前19位与m4是相同的,由此可以得到m4的前19位
又因为**m4后13位 ^ m4>>19的后13位 = m4后13位 ^ m4的前13位 = cipher的后13位**
所以**cipher的后13位 ^ m4的前13位 = m4后13位**
由此可以得到完整的m4
解密代码如下:
```python
block = bytes_to_long((cipher[i * 4: i * 4 + 4]))
block = bin(block)[2:].zfill(32)
# step4 decode
m4 = block[:19] + bin(int(block[:13], 2) ^ int(block[19:], 2))[2:].zfill(13)
print(m4)
print(long_to_bytes(int(m4, 2)))
第三步操作
首先要知道,"<<"是二进制位向左移动,高位丢弃,低位补0;"&"是按位做与运算,有0出0,全1出1 经过前两步加密的二进制结果为(当然,解密时以下结果是未知的): 00100001101100011110111001110111记为m3 00100001101100011 11011100111011100000000000000000 高位丢弃<- ->低位补0 接下来是与运算 首先看bin(2245263360)='0b10000101110101000000000000000000' 发现2245263360的后17位都是0 那么,m3<<17的后17位 & 2245263360的后17位 = 00000000000000000(17个0) 与第四步解密操作相同,就可以得到m3的后17位就是m4的后17位 又因为m3前15位 ^ m3<<17的前15位 & 2245263360的前15位 = m3前15位 ^ m3的后15位 & 2245263360的前15位 = m4的前15位 由此可以得到完整的m3 解密代码如下: 1
2
3
4
5# step3 decode
block = m4
m3 = bin((int(block[17:], 2) & int(bin(2245263360)[2:].zfill(32)[:15], 2)) ^ int(block[:15], 2))[2:].zfill(15) + block[15:]
print(m3)
print(long_to_bytes(int(m3, 2)))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
56from Crypto.Util.number import *
flag = 'abcd'
def convert(m):
m = m ^ m >> 13
m = m ^ m << 9 & 2029229568
m = m ^ m << 17 & 2245263360
m = m ^ m >> 19
return m
def transform(message):
assert len(message) % 4 == 0
new_message = ''
for i in range(len(message) // 4):
block = message[i * 4: i * 4 + 4]
block = bytes_to_long(block.encode())
block = convert(block)
block = bin(block)[2:].zfill(32)
new_message += block
return long_to_bytes(int(new_message, 2))
cipher = transform(flag)
print(bin(bytes_to_long(cipher))[2:].zfill(32))
print(cipher)
plain = ''
for i in range(len(cipher) // 4):
block = bytes_to_long((cipher[i * 4: i * 4 + 4]))
block = bin(block)[2:].zfill(32)
# step4 decode
m4 = block[:19] + bin(int(block[:13], 2) ^ int(block[19:], 2))[2:].zfill(13)
print(m4)
print(long_to_bytes(int(m4, 2)))
# step3 decode
block = m4
m3 = bin((int(block[17:], 2) & int(bin(2245263360)[2:].zfill(32)[:15], 2)) ^ int(block[:15], 2))[2:].zfill(15) + block[15:]
print(m3)
print(long_to_bytes(int(m3, 2)))
# step2 decode
block = m3
m2 = bin((int(block[23:], 2) & int(bin(2029229568)[2:].zfill(32)[14:23], 2)) ^ int(block[14:23], 2))[2:].zfill(9) + block[23:]
block = m3[:14] + m2
m2 = bin((int(block[14:23], 2) & int(bin(2029229568)[2:].zfill(32)[5:14], 2)) ^ int(block[5:14], 2))[2:].zfill(9) + block[14:]
block = m3[:5] + m2
m2 = bin((int(block[9:14], 2) & int(bin(2029229568)[2:].zfill(32)[:5], 2)) ^ int(block[:5], 2))[2:].zfill(5) + block[5:]
print(m2)
print(long_to_bytes(int(m2, 2)))
# step1 decode
block = m2
m1 = block[:13] + bin(int(block[:13], 2) ^ int(block[13:26], 2))[2:].zfill(13)
block = m1 + block[26:]
m1 = block[:26] + bin(int(block[13:19], 2) ^ int(block[26:], 2))[2:].zfill(6)
print(m1)
print(long_to_bytes(int(m1, 2)))
plain += m1
print(long_to_bytes(int(plain, 2))) 然后就把测试解密的代码应用于transformed_flag上 这里有个地方有点麻烦,就是原加密代码还要一步:
transformed_flag = transform(flag[5:-1].decode('hex')).encode('hex')
而python3没有decode('hex')
和encode('hex')
,可以参考Python3 字符串与hex之间的相互转换 .decode('hex')
对应于bytes.fromhex()
,.encode('hex')
对应于.hex()
所以,完整的解密代码如下:
1 | from Crypto.Util.number import * |
结果为: 即为flag
未曾设想的道路
正如wp中所说,还有一种通过反复加密得到结果的解法 这里给出python3的代码: 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
32from Crypto.Util.number import *
def convert(m):
m = m ^ m >> 13
m = m ^ m << 9 & 2029229568
m = m ^ m << 17 & 2245263360
m = m ^ m >> 19
return m
def transform(message):
assert len(message) % 4 == 0
new_message = b''
for i in range(len(message) // 4):
block = message[i * 4: i * 4 + 4]
block = bytes_to_long(block)
block = convert(block)
block = long_to_bytes(block, 4)
new_message += block
return new_message
transformed_flag = '641460a9e3953b1aaa21f3a2'
cipher = bytes.fromhex('641460a9e3953b1aaa21f3a2')
# assert (transform(bytes.fromhex('84b45f89af22ce7e67275bdc')).hex() == transformed_flag)
c = cipher
s = set()
while True:
c = transform(c.zfill(len(cipher)))
print(c.hex())
s.add(c.hex())
print(len(s))
if c.hex() == transformed_flag:
break 不难发现其周期为61319
更多关于MT19937伪随机数生成算法的体型可以参考madmonkey前辈的浅析mt19937伪随机数生成算法 其中介绍了与第一种方法相同的解法,只不过和wp一样采用模块化的代码,将解密步骤打包成函数 这里给出参照其中代码的解密代码:
1 | from Crypto.Util.number import * |
结果是相同的: 另一种方法是黑箱方法,将密文和明文的二进制编码视为两个向量\(a,b\),而由加密方法可知,两个向量存在线性关系,即存在一个方阵\(M\),使得\(a=Mb\) 具体线性关系如下:
这种方法没怎么看懂,以后有机会再说(溜了溜了)
结语
昨天(7.24)写了一点,但没写完 因为方法二的代码出了bug,怎么都没法解决,感谢Phoenix大佬帮我改了 今天(7.25)争取加更一期 (你问我7.23干嘛去了?补了一天番[doge]) 希望继续坚持