Contents

长安战疫(Misc+Crypto)赛题复现

0x00 前言

长安战疫的题目还挺友好的,但有的知识点当时没有联系到,赛后做个复现,总结学习一下。

0x01 no_math_no_cry

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from Crypto.Util.number import*
from secret import flag

assert len(flag) <= 80
def sec_encry(m):
    cip = (m - (1<<500))**2 + 0x0338470
    return cip

if __name__ == "__main__":
    m = bytes_to_long(flag)
    c = sec_encry(m)
    print(c)

# 10715086071862673209484250490600018105614048117055336074437503883703510511248211671489145400471130049712947188505612184220711949974689275316345656079538583389095869818942817127245278601695124271626668045250476877726638182396614587807925457735428719972874944279172128411500209111406507112585996098530169

当时比赛时是直接推出cip = gmpy2.iroot((c - 0x0338470),2)[0] + (1<<500),结果死活求不出flag,赛后才发现开平方后可能是负数,所以正确的脚本应该如下:

def sec_decry(c):
    cip1 = (1<<500) - gmpy2.iroot((c - 0x0338470),2)[0] 
    cip2 = (1<<500) + gmpy2.iroot((c - 0x0338470),2)[0] 
    return cip1,cip2

if __name__ == "__main__":
    m1,m2 = sec_decry(c)
    flag1 = long_to_bytes(m1)
    flag2 = long_to_bytes(m2)
    print('flag1:',flag1,'\nflag2:',flag2)

0x02 无字天书

追踪http流,发现504b开头的数据,疑似zip包,把他导出来。

解压出来得到两个东西,都是空白的。

key.ws是whitespace编码,导入https://www.dcode.fr/whitespace-language,可以解出key值为XiAnWillBeSafe

flag.txt是snow编码,利用工具SNOW.exe -p "XiAnWillBeSafe" -C flag.txt求解,特别注意密码要用双引号不能是单引号,最后得到cazy{C4n_y0u_underSt4nd_th3_b0oK_With0ut_Str1ng}

0x03 Ez_Steg

压缩包提示Password is six number,爆破得出220101,然后得到emoji.txt和一个pyc文件,起初以为pyc文件是要反编译成py然后求出key,和emoji通过解密得到flag。后来看wp发现,pyc文件存在Pyc字节码隐写,找到魔改的https://github.com/c10udlnk/stegosaurus后,执行./stegosaurus -x steg.pyc对pyc文件的隐藏文本进行提取,最后得到St3g1sV3ryFuNny

然后在https://aghorler.github.io/emoji-aes/进行求解,得到cazy{Em0j1s_AES_4nd_PyC_St3g_D0_yoU_l1ke}

0x04 西安加油-gaps解法

因为图片都是规则的正方形,所以可以考虑使用imagick结合gaps,使用magick montage *.png -tile 8x6 -geometry +0+0 flag.png,得到一张初具轮廓的图。

看下单张的详细信息

执行gaps --image=flag.png --generation=30 --population=300 --size=100得出完整拼图。

0x05 math

1
2
3
4
5
pinvq:0x63367a2b947c21d5051144d2d40572e366e19e3539a3074a433a92161465543157854669134c03642a12d304d2d9036e6458fe4c850c772c19c4eb3f567902b3
qinvp:0x79388eb6c541fffefc9cfb083f3662655651502d81ccc00ecde17a75f316bc97a8d888286f21b1235bde1f35efe13f8b3edb739c8f28e6e6043cb29569aa0e7b
c:0x5a1e001edd22964dd501eac6071091027db7665e5355426e1fa0c6360accbc013c7a36da88797de1960a6e9f1cf9ad9b8fd837b76fea7e11eac30a898c7a8b6d8c8989db07c2d80b14487a167c0064442e1fb9fd657a519cac5651457d64223baa30d8b7689d22f5f3795659ba50fb808b1863b344d8a8753b60bb4188b5e386
e:0x10005
d:0xae285803302de933cfc181bd4b9ab2ae09d1991509cb165aa1650bef78a8b23548bb17175f10cddffcde1a1cf36417cc080a622a1f8c64deb6d16667851942375670c50c5a32796545784f0bbcfdf2c0629a3d4f8e1a8a683f2aa63971f8e126c2ef75e08f56d16e1ec492cf9d26e730eae4d1a3fecbbb5db81e74d5195f49f1

先整理一下已知量,e、d、c和 invert(p,q) 、invert(q,p),设置invert(p,q) 、invert(q,p)分别为_q和_p。参考:https://scerush.github.io/2020/09/17/ctf-show-unusualrsa4/

e*d=1+k*phi,由此可以暴力枚举k值来破解phi。

p*_q mod q ≡1,q*_p mod p ≡1

phi = (p - 1) * (q - 1) ≡ n - p - q+1

(_p * phi) mod p ≡ _p * (n - p - q + 1) mod p ≡ (_p - _p*q) mod p ≡ (_p - 1) mod p

(_p * phi + 1 - _p) ≡  0 mod p

设(_p * phi + 1 - _p)为X,p能被X整除

_p * q ≡ 1 mod p 可计算出 q ,最后得到m。

脚本如下:

from gmpy2 import *
from Crypto.Util.number import *

_p = qinvp
poss_phi = []
for i in range(1,e):
	phi = e * d -1 
	if phi%i == 0:
		phi = phi//i
		dd = invert(e,phi)
		if dd == d:
			poss_phi.append(phi)

for phi in poss_phi:
	try:
		x = 1 + _p * phi - _p
		y1 = pow(5, phi, x) - 1
		y2 = pow(3, phi, x) - 1
		y3 = pow(2, phi, x) - 1
		p = gcd(gcd(y1, y2),y3)
		q = invert(_p, p)
		n = p * q
		m = pow(c, d, n)
		flag = long_to_bytes(m)
		if b'flag' in flag:
			print(flag)
	except:
		continue

0x06 ez_Encrypt

追踪流,在第10个流发现疑似base64的字符串,导出解码后得到一个压缩包。

发现是ThinkPHP6的源码

D盾扫描发现可疑文件。

打开/app/contoller/Index.php发现存在php代码混淆。

将eval改成echo,在线运行,查看相应的结果。

得到结果后,将eval改成echo,再次运行。

1
$VCBZQW="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";eval('?>'.$arCiCL($VvUrBZ($DEomKk($VCBZQW,$LnpnvY*2),$DEomKk($VCBZQW,$LnpnvY,$LnpnvY),$DEomKk($VCBZQW,0,$LnpnvY))));

一直向下解码,最后发现cazy{PHP_ji4m1_1s_s00000_3aSyyyyyyyyyyy}

0x07 LinearEquations

 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
from Crypto.Util.number import*
from secret import flag
assert flag[:5] == b'cazy{'
assert flag[-1:] == b'}'
flag = flag[5:-1]
assert(len(flag) == 24)

class my_LCG:
    def __init__(self, seed1 , seed2):
        self.state = [seed1,seed2]
        self.n = getPrime(64)
        while 1:
            self.a = bytes_to_long(flag[:8])
            self.b = bytes_to_long(flag[8:16])
            self.c = bytes_to_long(flag[16:])
            if self.a < self.n and self.b < self.n and self.c < self.n:
                break
    
    def next(self):
        new = (self.a * self.state[-1] + self.b * self.state[-2] + self.c) % self.n
        self.state.append( new )
        return new

def main():
    lcg = my_LCG(getRandomInteger(64),getRandomInteger(64))
    print("data = " + str([lcg.next() for _ in range(5)]))
    print("n = " + str(lcg.n))

if __name__ == "__main__":
    main() 

# data = [2626199569775466793, 8922951687182166500, 454458498974504742, 7289424376539417914, 8673638837300855396]
# n = 10104483468358610819

a、b、c是未知的,求解拼凑后能够得到flag。首先我们要弄清楚lcg.next()的算法过程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
data[0] = (a * seed2 + b * seed1 + c) mod n
data[1] = (a * data[0] + b * seed2 + c) mod n
data[2] = (a * data[1] + b * data[0] + c) mod n
data[3] = (a * data[2] + b * data[1] + c) mod n
data[4] = (a * data[3] + b * data[2] + c) mod n

设t[x] = (data[x + 1] - data[x] ) mod n
t[0] = (data[1] - data[0] ) mod n
t[1] = (data[2] - data[1] ) mod n
t[2] = (data[3] - data[2] ) mod n = [a * data[2] + b * data[1] + c - (a * data[1] + b * data[0] + c)] mod n = [ a * (data[2] - data[1]) + b * (data[1] - data[0])] mod n = (a * t[1] + b * t[0]) mod n
t[3] = (data[4] - data[3] ) mod n = (a * t[2] + b * t[1]) mod n

t[0]到t[3]都能够求解出来,可以根据他们的值联立方程组,求解a和b

先消去b,求解a
t[1] * t[2] = (a * t[1] * t[1] + b * t[0] * t[1]) mod n
t[0] * t[3] = (a * t[2] * t[0] + b * t[1] * t[0]) mod n
得到下式
(t[2] * t[1] - t[3] * t[0]) = [a * (t[1] * t[1] - t[2] * t[0])] mod n
而后求出b、c,解出flag

脚本如下

t = []

for i in range(1,len(data)):
	tmp = data[i] - data[i - 1]	
	t.append(tmp)
    
a = (t[2] * t[1] - t[3] * t[0]) * gmpy2.invert(t[1] ** 2 - t[2] * t[0],n) % n
b = (t[2] - a * t[1]) * gmpy2.invert(t[0],n) % n
c = (data[4] - a * data[3] - b * data[2]) % n

flag = b'cazy{' + long_to_bytes(a) + long_to_bytes(b) + long_to_bytes(c) + b'}'
print(flag)

得到flag

0x08 pipicc

用010打开bmp发现IHDR,但没有PNG头,修改文件。

删去bmp头,另存为png

得到图片

stegsolve提取蓝色低位数据,可以看到d9ff,ffd9是jpg文件尾的标识。

搜索FF D8 FF找到jpg的文件尾

删去后续数据后,在https://www.sweetscape.com/010editor/repository/scripts/file_info.php?file=StringReverse.1sc&type=1&sort=下载插件stringreverse.1sc逆转后保存为jpg文件。

得到flag