SWPUCTF -- Crypto

Web狗做不来Misc所以把Crypto给AK了

happy

一开始题目上错附件浪费快两个小时,有点无语

1
2
3
4
('c=', '0x7a7e031f14f6b6c3292d11a41161d2491ce8bcdc67ef1baa9eL')
('e=', '0x872a335')
#q + q*p^3 = 1285367317452089980789441829580397855321901891350429414413655782431779727560841427444135440068248152908241981758331600586
#qp + q *p^2 = 1109691832903289208389283296592510864729403914873734836011311325874120780079555500202475594

a=q+qp3a = q+q*p^3
b=qp+qp2b = q*p+q*p^2
x=gcd(a,b)x = gcd(a,b)
ab=q+qp3qp+qp2=1+p3p+p2=a/xb/x\frac{a}{b} = \frac{q+q*p^3}{q*p+q*p^2} = \frac{1+p^3}{p+p^2} = \frac{a/x}{b/x}
因式分解
1+p3p+p2=(1+p)(p2p+1)p(1+p)=p2p+1p\frac{1+p^3}{p+p^2} = \frac{(1+p)*(p^2-p+1)}{p*(1+p)} = \frac{p^2-p+1}{p}
这里b/x的值为素数,尝试作为p进行解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import gmpy2
from Crypto.Util.number import *
c = 0x7a7e031f14f6b6c3292d11a41161d2491ce8bcdc67ef1baa9e
e = 0x872a335
#q + q*p^3 = 1285367317452089980789441829580397855321901891350429414413655782431779727560841427444135440068248152908241981758331600586
#qp + q *p^2 = 1109691832903289208389283296592510864729403914873734836011311325874120780079555500202475594
a = 1285367317452089980789441829580397855321901891350429414413655782431779727560841427444135440068248152908241981758331600586
b = 1109691832903289208389283296592510864729403914873734836011311325874120780079555500202475594
x = gmpy2.gcd(a, b)
p = b // x
q = b // (p + p ** 2)
n = p * q
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))

Yusa的密码学课堂 CBC第一课

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
56
57
58
59
60
from Crypto.Cipher import AES
import os
flag='flag{********************************}'
BLOCKSIZE = 16

def pad(data):
pad_len = BLOCKSIZE - (len(data) % BLOCKSIZE) if len(data) % BLOCKSIZE != 0 else 0
return data + "=" * pad_len

def unpad(data):
return data.replace("=","")

def enc(data,key,iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypt = cipher.encrypt(pad(data))
return encrypt

def dec(data,key,iv):
try:
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypt = cipher.decrypt(data)
return unpad(encrypt)
except:
exit()

def task():
try:
key = os.urandom(16)
iv = os.urandom(16)
pre = "yusa"*4
for _ in range(3):
choice = raw_input(menu)
if choice == '1':
name = raw_input("What's your name?")
if name == 'admin':
exit()
token = enc(pre+name,key,iv)
print "Here is your token(in hex): " + iv.encode('hex') + token.encode('hex')
continue
elif choice == '2':
token = raw_input("Your token(in hex): ").decode('hex')
iv = token[:16]
name = dec(token[16:],key,iv)
print iv.encode('hex') + name.encode('hex')
if name[:16] == "yusa"*4:
print "Hello, " + name[16:]
if name[16:] == 'admin':
print flag
exit()
else:
continue
except:
exit()
menu='''
1. register
2. login
3. exit
'''
if __name__ == "__main__":
task()

CBC Byte Flip

00.png

如图所示
我们要把plain2中的某一字节翻转为另一字节
由于C1来自于cipher2进行Block Cipher Decryption之后的结果
而且key未知,就不能直接得知C1的值
但是字节翻转的妙处在于通过修改上一组的密文来翻转下一组的明文,从而可以完全忽视这一点

由异或运算可以推导
B1 = A1 xor C1
那么也有C1 = A1 xor B1
B1’ = A1’ xor C1
(A1’是修改之后的密文字节,B1’是翻转之后的明文字节)
而如果我们能够修改cipher1,那么就能够修改A1的值
即A1’ = A1 xor B1 xor B1’
A1’ xor C1 = A1 xor B1 xor B1’ xor C1 = C1 xor B1’ xor C1 = B1’

用三条算式来表述上面的话就是

B = A xor C
B’ = A’ xor C
A’ = A xor B xor B’
字节翻转的要求也就显而易见了

对于A完全可控
已知B的值
到这里也只完成了一半
由于修改了A1
cipher1在进行BCD的时候会得出错误的结果
再与IV相异或则会导致plain1出错

如果能够得到修改A1之后产生的错误的plain1的值
而且IV可以完全控制的话
那么就能够把刚才的把戏再玩一遍

B2 = A2 xor C2
B2’ = A2’ xor C2
A2’ = A2 xor B2 xor B2’
A2为原IV
B2是错误的plain1
B2’是正确的plain1

BTW:每一组Cipher的长度为16Byte

以上大概介绍了CBC字节翻转的攻击方式
需要密文以及IV可控来改写明文

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
from pwn import *
import binascii
import time

p = remote('das.wetolink.com',42888) # 连接
p.recvuntil(b"3. exit\n")

p.sendline(b"1") # 注册
p.recv()
p.sendline(b"Admin") # 用户名为Admin,方便之后修改
data0 = p.recvline().decode()
data0 = data0[28:124] # 提取返回数据部分
iv0 = data0[:32] #返回的IV
cipher00 = data0[32:64] #"yusayusayusayusa"的加密结果
cipher01 = data0[64:96] #"Admin"的加密结果
replacement = str(hex(int(cipher00[:2], 16) ^ ord("A") ^ ord("a")))[2:] # 计算替换密文的值
payload0 = iv0 + replacement + cipher00[2:] + cipher01 # 发送替换密文

#print("data0: " + data0)
#print("payload0: " + payload0)

p.recvuntil(b"3. exit\n")
p.sendline(b"2") # 登入
p.recv()
p.sendline(payload0.encode()) # 发送Payload0
data1 = p.recvline()[:-1].decode() # 得到返回的数据

plain1 = data1[32:64] # "yusayusayusayusa"由于密文被替换,解出来的明文是错误的,之后可以进行异或修改
#print("data1: " + data1)
#print("plain1: " + plain1)
iv1 = str(hex(int(binascii.hexlify("yusa".encode()).decode() * 4,16) ^ int(plain1, 16) ^ int(iv0, 16)))[2:] # 计算IV,用于修改错误的明文
#print("iv1: ",iv1)
payload1 = iv1 + replacement + cipher00[2:] + cipher01
#print("payload1: " + payload1)
p.recvuntil(b"3. exit\n")
p.sendline(b"2") # 登入
p.recv()
p.sendline(payload1.encode()) # 发送Payload1
p.recvuntil(b"admin\n")
print(p.recvline()) # 得到flag

Yusa的密码学课堂 ECB

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
from Crypto.Cipher import AES
import os
BLOCKSIZE = 16
flag='flag{********************************}'

def pad(data):
pad_len = BLOCKSIZE - (len(data) % BLOCKSIZE) if len(data) % BLOCKSIZE != 0 else 0
return data + chr(pad_len) * pad_len

def unpad(data):
num = ord(data[-1])
return data[:-num]

def enc(data,key):
cipher = AES.new(key, AES.MODE_ECB)
encrypt = cipher.encrypt(pad(data))
return encrypt

def dec(data,key):
try:
cipher = AES.new(key, AES.MODE_ECB)
encrypt = cipher.decrypt(data)
return unpad(encrypt)
except:
exit()

def task():
try:
key = os.urandom(16)
while True:
plaintext = raw_input("Amazing function: ").decode('hex')
yusa = plaintext + flag
print enc(yusa,key).encode('hex')
except Exception as e:
print str(e)
exit()
if __name__ == "__main__":
task()

这里的加密的明文为"输入的hex转字符"+“flag”+Padding
如果发送的明文为
aaaaaaaaaaaa
那么加密的明文为
aaaaaaaaaaaaflag{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}
分组Padding之后的结果为
aaaaaaaaaaaaflag {XXXXXXXXXXXXXXX XXXXXXXXXXXXXXXX X}+Padding
分析pad函数我们可以知道Padding的值
而ECB的加密方式就是分组明文密文之间没有关联
那么我们就可以构造 “?}+Padding"的明文去加密,?作为掩码,flag内容长度为32字节,目测是md5,所以字符集目测是"0123456789abcdef”
然后我们在字符集里去爆破一位掩码,将得到的结果与加密结果的最后一个分组进行比较,若相同则我们得到了flag最后一位的字节

理解了上述内容之后就可以写一个循环来逐位爆破flag,每次初始填充的a多一位
a在变长的过程中需要注意明文长度会多出几个分组,从而导致密文也多出几个分组

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
from pwn import *
import binascii
import time

plain = "flag{********************************}"
flag = "}"
BLOCKSIZE = 16
char = '0123456789abcdef'
p = remote('das.wetolink.com',42887)
text = p.recv()
for pad1 in range(12, 46):
#print('61'*pad1)
p.sendline('61'*pad1)
text = p.recvline()
print(len(text))
if len(text) <= 147:
res = text.decode()[-33:-1]
elif len(text) <= 179:
res = text.decode()[-33-32:-1-32]
else:
res = text.decode()[-33-64:-1-64]
#print(res)
pad2 = (26-pad1)%16
#print(pad1)
for j in char:
time.sleep(0.1)
payload = str(hex(ord(j)))[2:]+binascii.hexlify(flag.encode()).decode()+('0'+str(hex(pad2)[2:]))*pad2
#print(payload)
p.sendline(payload)
text = p.recvline().decode()
#print(text)
if res == text[18:18+32]:
flag = j + flag
print(flag)
break
flag = "flag{" + flag
print(flag)