BUUCTF 每日打卡 2021-7-24

引言

台风天,还练了一天车,淦

[SUCTF2019]MT

来填坑了
首先看加密代码:

from Crypto.Util import number
from flag import flag

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 = number.bytes_to_long(block)
        block = convert(block)
        block = number.long_to_bytes(block, 4)
        new_message += block
    return new_message

transformed_flag = transform(flag[5:-1].decode('hex')).encode('hex')
print 'transformed_flag:', transformed_flag
# transformed_flag: 641460a9e3953b1aaa21f3a2

看到这个convert函数,我就有些胆怯了
这个位运算看起来完全打乱了加密内容(而且之前碰到类似的题目都没做出来)
尤其是第二三步,感觉完全乱了
看了一眼别人的wp,受到了启发
发现bin(2029229568)后9位都是0,而bin(2245263360)后17位(其实是18位)都是0,与移位的位数相同,那就好办了
首先要明确一件事:位运算符的优先级 移位(>>和<<) > 与运算(&) > 异或运算(^)
就以’abcd’为例,推出第四和第三步的解密过程(第一和第二步同理)
因为我没有装python2的环境,就用python3把加密代码重写了一遍,做了一点小修改
代码如下:

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

第四步操作

首先要知道,">>"是二进制位向右移动,低位丢弃,高位补0
经过前三步加密的二进制结果为(当然,解密时以下结果是未知的):
10100101011101011110111001110111记为m4
那么 m4>>19 的结果为
0000000000000000000101001010111 01011110111001110111
高位补0<- ->低位丢弃
接下来是异或运算
解密需要用到异或运算的两个性质:
1、若a=b ^ c,则c=a ^ b
2、a=a ^ 0
cipher=m4 ^ m4 >> 19,其中cipher是已知的
按位加密的过程中,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
解密代码如下:

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
解密代码如下:

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

第二和第一步的解密类似,就是需要重复操作几次
完整的测试解密代码如下:

from 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()
所以,完整的解密代码如下:

from Crypto.Util.number import *

cipher = bytes.fromhex('641460a9e3953b1aaa21f3a2')
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)))
print(long_to_bytes(int(plain, 2)).hex())

结果为:
在这里插入图片描述
即为flag

未曾设想的道路

正如wp中所说,还有一种通过反复加密得到结果的解法
这里给出python3的代码:

from 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

至于为什么,是因为本题使用的加密算法是梅森旋转算法(Mersenne twister),是一种伪随机数生成算法,该算法的一个更新的和更常用的是MT19937, 32位字长,对应了题目
并且该算法生成的随机数具有周期性,这也就不难理解为什么一直加密密文能得到明文,因为经过一个周期后得到的还是密文,那么上一个就是明文了
上述解密代码结果为:
在这里插入图片描述
不难发现其周期为61319

更多关于MT19937伪随机数生成算法的体型可以参考madmonkey前辈的浅析mt19937伪随机数生成算法
其中介绍了与第一种方法相同的解法,只不过和wp一样采用模块化的代码,将解密步骤打包成函数
这里给出参照其中代码的解密代码:

from Crypto.Util.number import *

# right shift inverse
def inverse_right(res, shift, bits=32):
    tmp = res
    for i in range(bits // shift):
        tmp = res ^ tmp >> shift
    return tmp


# right shift with mask inverse
def inverse_right_mask(res, shift, mask, bits=32):
    tmp = res
    for i in range(bits // shift):
        tmp = res ^ tmp >> shift & mask
    return tmp

# left shift inverse
def inverse_left(res, shift, bits=32):
    tmp = res
    for i in range(bits // shift):
        tmp = res ^ tmp << shift
    return tmp


# left shift with mask inverse
def inverse_left_mask(res, shift, mask, bits=32):
    tmp = res
    for i in range(bits // shift):
        tmp = res ^ tmp << shift & mask
    return tmp


def extract_number(m):
    m = m ^ m >> 13
    m = m ^ m << 9 & 2029229568
    m = m ^ m << 17 & 2245263360
    m = m ^ m >> 19
    return m & 0xffffffff

def recover(y):
    y = inverse_right(y, 19)
    y = inverse_left_mask(y, 17, 2245263360)
    y = inverse_left_mask(y, 9, 2029229568)
    y = inverse_right(y, 13)
    return y & 0xffffffff

transformed_flag = '641460a9e3953b1aaa21f3a2'
cipher = bytes.fromhex('641460a9e3953b1aaa21f3a2')
new_message = b''
for i in range(len(cipher) // 4):
    block = cipher[i * 4: i * 4 + 4]
    block = bytes_to_long(block)
    block = recover(block)
    block = long_to_bytes(block, 4)
    new_message += block
print(new_message.hex())

结果是相同的:
在这里插入图片描述
另一种方法是黑箱方法,将密文和明文的二进制编码视为两个向量 a , b a,b a,b,而由加密方法可知,两个向量存在线性关系,即存在一个方阵 M M M,使得 a = M b a=Mb a=Mb
具体线性关系如下:
在这里插入图片描述
这种方法没怎么看懂,以后有机会再说(溜了溜了)

结语

昨天(7.24)写了一点,但没写完
因为方法二的代码出了bug,怎么都没法解决,感谢Phoenix大佬帮我改了
今天(7.25)争取加更一期
(你问我7.23干嘛去了?补了一天番[doge])
希望继续坚持

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值