简介
这道题比较老了,但是质量还是不错的,本来应该是跟SM加密方式有关的,但是脚本能直接读懂,那就不必再去查了。
题解
分析函数
首先分析一下题目的代码,从运行的顺序来逐个分析函数
直接调用的函数是run()
这个函数内部最关键的是这个key,这是AES加密的密钥,有了key,才能从ef中的密文还原出flag。
再看key的来源:
虽然有一大串式子,但是都是在以固定的方式变换形式,真正影响key的只有choose这个变量。
顺势就看看choose怎么求:
仔细看这段代码,大概意思是,从前往后遍历choose的512个比特位,如果值为一则异或ps中的对应数。
我们知道异或是可逆的,如果我们知道r都异或了ps中的哪些数是不是就知道bchoose的哪些位是1了?那么不就知道choose是啥了。
但是单纯来想,已知一个512个数的数表和在其中挑选若干个进行异或的结果,这个算法要跑出来都异或了哪些数,这计算量得到2^512(天文数字中的天文数字),所以必定是有一些特殊结构简化了计算。因而解出choose的关键就在这个生成ps的**gen512num()**这个函数有什么特殊结构了。
分析该函数:主要操作是生成512个相似结构的512位数字,每个数都由一个长度不定的前缀和随后的0填充组成。对异或来讲,只有这个前缀是有意义的。
虽然是通过随机数生成的,但事实上这个前缀的长度是可以反算出来的,不妨试一下
脚本如下:
f = open("ps","r")
line = f.readlines()
s=[]
for i in range(len(line)):
line[i] = int(line[i].strip('\n'))
t=line[i]
len=0
while(t%2==0):
len+=1
t=t>>1
s.append(512-len)
#s.sort() 一旦排序了就看出其中玄机了
print(s)
仔细观察s的结果,不难发现s正好是1-512的无重复数组,但是注意,我们可以排序了之后观察,但是不能在后面使用排序过后的s,因为解出choose是要知道ps中哪几行的数被异或过,那么这些数原来的顺序也是必要的信息,不能擅自修改。
还原choose
至此,前缀长度是1-512的不重复数组这个特殊结构,让我们反推choose的算法难度大幅降低(但是这很不现实,随机生成的长度这么巧的也太难了)
这是因为比如最后一个值为1的位,导致它值为1的可能,只有前缀长为它对应位置的那个数(因为前缀的结尾必为1)
异或这个数把这个数造成的影响消除,这样从后往前找可以再发现一个位仅受ps中的一个数的影响的情况,依次类推,就可以还原出 r 到底异或了哪些数,找到都是异或了哪些数,根据 r 的产生的方式,我们就能知道choose的哪些位是1。
脚本如下:
f1=open("r","r")
k=int(f1.read())
m=list("0"*512) #bchoose
for i in range(511,-1,-1):
if((k>>(511-i))%2==1):
k=k^line[s.index(i+1)]
m[s.index(i+1)]= "1"
else:
m[s.index(i+1)]="0"
choose=int("0b"+"".join(m),2)
#choose=11400599473028310480620591074112690318799501642425666449519888152497765475409346201248744734864375690112798434541063767944755958258558428437088372812844657
解密
由于我们已经还原出了choose,那么key只需要按照题目给的方法生成就可以了,随后先进行base64解码,再进行AES解密,就得到最后的结果啦。
key=long_to_bytes(int(hashlib.md5(long_to_bytes(choose)).hexdigest(),16))
aes_obj = AES.new(key, AES.MODE_ECB)
f2=open("ef","rb")
ef=f2.read()
ef=base64.b64decode(ef)
result=aes_obj.decrypt(ef)
print(result)
FLAG:flag{shemir_alotof_in_wctf_fun!}