ByteCTF之密码题lrlr

赛题下载链接:lrlr
下面是这道题的python代码部分:

from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes
from Crypto.Cipher import AES
import random


class lrand:

    def __init__(self, seed):
        self.seed = seed
        self.states = self.gen_states()
        self.statespoint = 0
        self.stateselse = 24

    def generate_init_state(self):
        a = 0
        for i in bin(self.seed)[2:]:
            a = a << 1
            if (int(i)):
                a = a ^ self.seed
            if a >> 256:
                a = a ^ 0x10000000000000000000000000000000000000000000000000000000000000223L
        return a

    def gen_states(self):
        init_state = self.generate_init_state()
        e = 17
        clist = []
        nlist = []
        for i in range(24):
            p = getPrime(512)
            q = getPrime(512)
            n = p * q
            c = pow(init_state, e, p * q)
            nlist.append(n)
            clist.append(c)
        f = open("cl", "wb")
        for i in nlist:
            f.write(hex(i) + "\n")
        return clist

    def stateconvert(self, state):
        key = long_to_bytes(random.getrandbits(128))
        handle = AES.new(key, AES.MODE_CBC, "\x00"*16)
        output = handle.encrypt(long_to_bytes(state))
        return output

    def gen_new_states(self):
        for i in range(24):
            key = long_to_bytes(random.getrandbits(128))
            handle = AES.new(key, AES.MODE_CBC, "\x00"*16)
            tmpstate=handle.encrypt(long_to_bytes(self.states[self.statespoint-24+i]))
            self.states.append(bytes_to_long(tmpstate))
        self.stateselse += 24

    def lrandout(self):
        if self.stateselse > 0:
            self.stateselse -= 1
            tmp = self.states[self.statespoint]
            self.statespoint += 1
            return self.stateconvert(tmp)
        else:
            self.gen_new_states()
            return self.lrandout()


def oldtest():
    f=open("old","wb")
    s=""
    for i in range(1000):
        s+=str(random.getrandbits(32))+"\n"
    f.write(s)


def newtest():
    handle = lrand(bytes_to_long(open("flag", "rb").read().strip()))
    for i in range(24):
        handle.lrandout()
    f = open("new", "wb")
    s = ""
    for i in range(24):
        s+=handle.lrandout().encode("hex")+"\n"
    f.write(s)


oldtest()
newtest()

经过仔细研究,我们可以发现,这份代码是先读取了flag,再对flag进行了generate_init_state函数的操作,生成了一个初始的state,再对这个state进行了24次不同模数的低指数的RSA加密(因为这里的模数非常大,不可能进行分解,而指数非常小,所以可以使用RSA广播攻击,即使用中国余数定理)。之后再对每个state分别进行了两次AES加密,即总共进行了48次AES加密,使用了48个随机生成的128bit秘钥,最后将加密得到的24个结果存放在了相应文件中。
有几个小问题:

  1. 如何获得我们所需的48个随机的AES秘钥
  2. generate_init_state()这个函数怎么反推得到原先的flag

针对第一个问题,注意到源代码中保存了1000个使用random生成的随机数,在好心人帮助下,知道了借助random_cracker这个工具(由此可见交流的重要性),由于random函数的伪随机性,我们可以推出接下来的随机值,得到我们所需要的秘钥。

针对第二个问题,我们可以进行一次试验,将该函数返回的结果重新输入到该函数,不断循环,总能得到最初的输入,即:这个算法只要将加密的密文按着加密脚本重复下去,就能得到flag,flag的格式一般为flag{.x},针对这次比赛,也可能是bytectf{.x},因此不断循环将每次循环得到的结果开头与上述格式对比,即可得到最终的flag。

整体思路是,先生成所需的随机数,再进行48次AES解密,再进行RSA广播攻击,最后循环那个函数得到flag,其中很多细节都在代码里。

解题代码如下:

import random,time
from randcrack import *
from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes
from Crypto.Cipher import AES
from gmpy2 import invert
import gmpy2
def AES_CBC_decrypto(value,key):#value is long type with 128 bits
	handle = AES.new(long_to_bytes(key), AES.MODE_CBC, b'\x00'*16)
	output = handle.decrypt(long_to_bytes(value))
	#print(type(output))
	return output
def generate_init_state(value):#an original number will return after execute this function for many times
    a = 0
    for i in bin(value)[2:]:#i from high bit of seed to low
        a = a << 1#except 0b
        if (int(i)):
            a = a ^ value# ^ means xor
        if a >> 256:
            a = a ^ 0x10000000000000000000000000000000000000000000000000000000000000223
    return a


rc=RandCrack()
old=open("old","rb").read().split()
for i in range(624):
	old[i]=int(old[i].decode("UTF-8"))
	rc.submit(old[i])#submit 624 number to generate the following random number
for i in range(1000-624):#generate the last (1000-624) 32-bit random number
	gg=rc.predict_getrandbits(32)


key=[]
newlist=[]
new=open("new","rb").read().split()
for i in range(len(new)):
	newlist.append(int('0x'+new[i].decode("UTF-8"),16))#transform the 16-bit results into decimal

for i in range(72):
	key.append(rc.predict_getrandbits(128))#Now we can predict the following random number

for i in range(48):
	newlist[(47-i)%24]=bytes_to_long(AES_CBC_decrypto(newlist[(47-i)%24],key[71-i]))
	#decrypt the output in newlist using AES with the key generated before

clist=newlist
nlist=open("cl","rb").read().split()

#Then we use RSA broadcast attack,the theory here is called CRT
M=1
for i in range(len(nlist)):
	#print(type(nlist[0]))
	nlist[i]=int(nlist[i].decode("UTF-8"),16)
	M*=nlist[i]

m=nlist
a=clist
c=[]
Mn=[1]*24
Mn_invert_m=[]
temp=0
for i in range(24):
	for j in range(24):
		if(j!=i):
			Mn[i]*=m[j] 
	Mn_invert_m.append(invert(Mn[i],m[i]))
	c.append(Mn_invert_m[i]*Mn[i])
	temp+=a[i]*c[i]
	
A=temp%M	
flag=gmpy2.iroot(A,17)[0]#use 'iroot' function to get the root of a very big number

for i in range(100000000):#the hex of flag is '666c6167',we use 'flag'.encode("UTF-8").hex() to get find out if we get the right answer
	flag=generate_init_state(flag)
	#if(flag==flag0):
		#break
	if(hex(flag)[2:10]=='666c6167'):# or hex(flag)[2:16]=='62797465637466'):
		print(long_to_bytes(flag).decode())
		#print(i)
		break

最后得到flag

flag{very_simple_random_problem}

你可能感兴趣的:(密码学,ctf,crypto)