2021天翼杯
crypto
TryHash
审计题目代码,题目给出了一个feistel结构的加密算法,密钥长度为8字节。用随机生成的密钥对flag进行了加密。攻击者可以提供一段明文让服务器用同样的密钥进行加密。
密钥长度为8字节,直接爆破的复杂度为2^64,是不太现实的。
本题密码算法的设计漏洞其实在于其轮函数f的设计。具体来说其轮函数具有较差的差分性质。
def g(self,v1,v2,x):
value = (v1+v2+x)%256
value = ((value<<3) | (value>>5)) &0xff
return value
def f(self,value):
v1,v2 = unpack('>2B',pack('>H',value))
v2 = self.g(v1,v2,1)
v1 = self.g(v1,v2,0)
value = unpack('>H',pack('>2B',v1,v2))
return value[0]
具体来说,通过数学推导,我们可以发现,对于f来说,当其两个输入的差分为0x8080时,其输出差分100%是0x400。根据这一差分性质,我们可以对该加密算法进行差分分析攻击。差分分析的具体原理可以参考这个blog http://www.theamazingking.com/crypto-feal.php
我们以对最后一轮加密(即第3轮)进行攻击为例,介绍攻击的流程。
我们构造两个特殊的输入 (L0,R0)和 (L0’,R0’)其中 L0 = L0‘, R0 = R0’^0x8080,让服务器加密,得到加密结果 (L3,R3),(L3’,R3’).通过对该加密算法的推导,我们可以得到关于第3轮轮函数f的运算关系。
f(round3_key^L0) = out1
f(round3_key^L0') = out2
out1^out2 = R0^L0^R0'^L0'^0x400
其中,只有round3_key是未知的,其他参数都是已知的。round3_key的大小为2个字节,完全可以通过爆破来得到正确的解。这样我们就把对于整个key的求解,拆分到对于轮密钥的求解,爆破复杂度从 2^64降低到了 2^16
需要注意的是对于一组明密文对,可能有多个符合关系的解,我们可以同时对多组明密文对进行求解,来过滤掉错误的解。
依次类推,可以用相似的方法得到第1,2,3轮的轮密钥。有了这三轮的轮密钥后,可以通过逆运算很块的求解出第0轮的密钥,最终恢复出整个密钥。
完整解题脚本
from hashlib import sha256
import random
from pwn import *
from pwnlib.util.iters import bruteforce
from struct import pack, unpack
def g(v1,v2,x):
value = (v1+v2+x)%256
value = ((value<<3) | (value>>5)) &0xff
return value
def f(value):
v1,v2 = unpack('>2B',pack('>H',value))
v2 = g(v1,v2,1)
v1 = g(v1,v2,0)
value = unpack('>H',pack('>2B',v1,v2))
return value[0]
def decrypt_ecb(cipher,key):
msg = ''
for i in range(0,len(cipher),4):
msg += decrypt(cipher[i:i+4],key)
return msg.strip('\x00')
def decrypt(msg,key):
subkeys = unpack('>4H',key)
left,right = unpack('>2H',msg)
left = right^left
for i in range(3):
left,right = right,left
left = left^f(subkeys[2-i]^right)
right = right^subkeys[3]
return pack('>2H', left, right)
def encrypt_ecb(msg,key):
l = len(msg)
if l%4 !=0:
msg = msg+'\x00'*(4-(l%4))
cipher = ''
for i in range(0,len(msg),4):
cipher += encrypt(msg[i:i+4],key)
return cipher
def encrypt(msg,key):
subkeys = unpack('>4H',key)
left,right = unpack('>2H',msg)
right = right^subkeys[3]
for i in range(3):
tmp = left^f(subkeys[i]^right)
left = right
right = tmp
left = right^left
return pack('>2H', left, right)
def dfa_f():
for i in range(1000):
input1 = random.randint(0,0xffff)
output1 = f(input1)
input2 = input1^0x8080
output2 = f(input2)
assert(output1^output2 == 0x400)
def genpayload1(num):
payload = ''
for i in range(num):
data1 = random.randint(0,0xffff)
data2 = random.randint(0,0xffff)
data2diff = data2^0x8080
payload += pack('>2H',data1,data2)
payload += pack('>2H',data1,data2diff)
return payload
def genpayload2(num):
payload = ''
for i in range(num):
data1 = random.randint(0,0xffff)
data2 = random.randint(0,0xffff)
data2diff = data2^0x400
payload += pack('>2H',data1,data2)
payload += pack('>2H',data1,data2diff)
return payload
def testkey_round3(pairs,key):
for pair in pairs:
output1 = pair[0]
output2 = pair[1]
output1_0,output1_1 = unpack('>2H',output1)
output2_0,output2_1 = unpack('>2H',output2)
f_out_diff = output1_1 ^ output2_1 ^0x400
f_in1 = key^output1_0^output1_1
f_in2 = key^output2_0^output2_1
if(f(f_in1)^f(f_in2)==f_out_diff):
continue
else:
return False
return True
def testkey_round2(pairs,key,r3key):
for pair in pairs:
output1 = pair[0]
output2 = pair[1]
output1_0,output1_1 = unpack('>2H',output1)
output2_0,output2_1 = unpack('>2H',output2)
output1_r3_1 = output1_0^output1_1
output2_r3_1 = output2_0^output2_1
f_out_diff = output1_r3_1^output2_r3_1^0x400
f_in1 = key^output1_1^f(r3key^output1_r3_1)
f_in2 = key^output2_1^ f(r3key^output2_r3_1)
if(f(f_in1)^f(f_in2)==f_out_diff):
continue
else:
return False
return True
def attack_round1(msg,cipher,keys):
ciphers = [cipher[i:i+4] for i in range(0,len(cipher),4)]
msgs = [msg[i:i+4] for i in range(0,len(msg),4)]
c = ciphers[0]
m = msgs[0]
output0,output1 = unpack('>2H',c)
output0 = output0^output1
input0,input1 = unpack('>2H',m)
candkeys = []
for key in keys:
r2k,r3k = key
output_r2_1 = output0
output_r2_0 = output1^f(r3k^output0)
output_r1_1 = output_r2_0
output_r1_0 = output_r2_1^f(r2k^output_r2_0)
k0 = output_r1_0^input1
for k in range(0x10000):
f_in = k^output_r1_0
f_out = output_r1_1^input0
if f(f_in) == f_out:
candkeys.append([k,r2k,r3k,k0])
return candkeys
def attack_round2(msg,cipher,keys):
ciphers = [cipher[i:i+4] for i in range(0,len(cipher),4)]
cipher_pairs = [(ciphers[i],ciphers[i+1]) for i in range(0,len(ciphers),2)]
candkeys = []
for r3k in keys:
for key in range(0x10000):
if testkey_round2(cipher_pairs,key,r3k):
candkeys.append([key,r3k])
return candkeys
def attack_round3(msg,cipher):
ciphers = [cipher[i:i+4] for i in range(0,len(cipher),4)]
cipher_pairs = [(ciphers[i],ciphers[i+1]) for i in range(0,len(ciphers),2)]
candkeys = []
for key in range(0x10000):
if testkey_round3(cipher_pairs,key):
candkeys.append(key)
return candkeys
def exploit():
con = remote('127.0.0.1',10005)
context.log_level = 'debug'
con.recvuntil("XXXX+")
d = con.recvuntil(")")[:-1]
con.recvuntil(" == ")
target = con.recvline().strip()
ans = bruteforce(lambda x: sha256(x+d).hexdigest() == target,string.letters+string.digits,4)
con.sendlineafter("Give me XXXX",ans)
con.recvuntil('is:')
flag = con.recvline().strip()
payload = genpayload1(6)+genpayload2(6)
con.sendlineafter(':',payload)
cipher = con.recv(len(payload))
cipher_round3 = cipher[:48]
msg_round3 = payload[:48]
possible_keys = attack_round3(msg_round3,cipher_round3)
print 'round3 keys maybe:', possible_keys
cipher_round2 = cipher[48:96]
msg_round2 = payload[48:96]
possible_keys = attack_round2(msg_round2,cipher_round2,possible_keys)
print 'round2 keys maybe:', possible_keys
possible_keys = attack_round1(msg_round2,cipher_round2,possible_keys)
print 'round1&0 keys maybe:',possible_keys
for key in possible_keys:
real_key = pack('>4H',*key)
print 'decrypt with key ',repr(real_key)
print repr(decrypt_ecb(flag,real_key))
con.close()
def exploit_local():
key = os.urandom(8)
print repr(key)
payload = genpayload1(6)+genpayload2(6)
cipher = encrypt_ecb(payload,key)
cipher_round3 = cipher[:48]
msg_round3 = payload[:48]
possible_keys = attack_round3(msg_round3,cipher_round3)
print 'round3 keys maybe:', possible_keys
cipher_round2 = cipher[48:96]
msg_round2 = payload[48:96]
possible_keys = attack_round2(msg_round2,cipher_round2,possible_keys)
print 'round2 keys maybe:', possible_keys
possible_keys = attack_round1(msg_round2,cipher_round2,possible_keys)
print 'round1&0 keys maybe:',possible_keys
flag = 'flag{test}'
flag = encrypt_ecb(flag,key)
print decrypt_ecb(flag,key)
for key in possible_keys:
real_key = pack('>4H',*key)
print 'decrypt with key ',repr(real_key)
print repr(decrypt_ecb(flag,real_key))
exploit()
babypack
由于题目中方案设计的缺陷,导致在公钥中
$$
2*A[i+1]-A[i]\approx n
$$
这其中插值于pq的乘积在高位比特上大多数是相等的,因此需要通过check功能拿到部分的低位比特。但仍然无法拿到完整的n,且就算拿到完整的n也无法通过分解来解出p,q。
这是注意发现,公钥A是通过UV和中国剩余定理来生成的,因此可以通过解二元一次方程得到uv值。再用公钥与uv的差值与n的最大公因数来确定n和得到p,q。
具体详见exp
from netcat import *
from copy import deepcopy
from gmpy2 import iroot, gcd
import time
start = time.time()
r = remote("47.100.138.126", 10002)
r.recv_until(b"your pubkey:\n")
s = r.recv_until(b"\n").strip().decode()
pub = eval(s)
add = int(r.recv_until(b"\n").strip().decode())
mult = int(r.recv_until(b"\n").strip().decode())
#print(add)
#print(mult)
M = max(2 * pub[2] - pub[1], 2 * pub[3] - pub[2])
Mbin = bin(M)[2:532]
n = ["0"] * M.bit_length()
for i in range(530):
n[i] = Mbin[i]
r.recv_until(b">")
r.sendline(b"1")
r.recv_until(b">")
tmp = int("".join(n), 2)
r.sendline(str(tmp).encode())
count = int(r.recv_until(b"\n").strip())
#print(count)
for i in range(498):
r.recv_until(b">")
r.sendline(b"1")
r.recv_until(b">")
n2 = deepcopy(n)
n2[530 + i] = str(int(n2[530 + i]) ^ 1)
tmp = int("".join(n2), 2)
r.sendline(str(tmp).encode())
count2 = int(r.recv_until(b"\n").strip())
if count2 > count:
n[530 + i] = str(int(n[530 + i]) ^ 1)
count = count2
#print(count)
M = int("".join(n), 2)
u = (add + iroot(add**2-4*mult, 2)[0]) // 2
v = (add - iroot(add**2-4*mult, 2)[0]) // 2
count = 0
while True:
tmp1 = gcd(pub[0] - u, M)
tmp2 = gcd(pub[0] - v, M)
if tmp1.bit_length() > 100 or tmp2.bit_length() > 100:
p = max(tmp1, tmp2)
break
else:
M += 1
count += 1
#print(count)
q = M // p
if p < q:
p, q = q, p
r.recv_until(b">")
r.sendline(b"2")
r.recv_until(b"\n")
s = r.recv_until(b"\n").strip().decode()
ct = int(s)
c1 = ct % p
c2 = ct % q
pt = abs(c1 - c2)
r.recv_until(b">")
r.sendline(str(pt).encode())
#print(pt)
print(r.recv(1024))
r.close()
end = time.time()
#print(end - start)
Crypto_mycipher
from hashlib import sha256
import random
from pwn import *
from pwnlib.util.iters import bruteforce
from struct import pack, unpack
def g(v1,v2,x):
value = (v1+v2+x)%256
value = ((value<<3) | (value>>5)) &0xff
return value
def f(value):
v1,v2 = unpack('>2B',pack('>H',value))
v2 = g(v1,v2,1)
v1 = g(v1,v2,0)
value = unpack('>H',pack('>2B',v1,v2))
return value[0]
def decrypt_ecb(cipher,key):
msg = ''
for i in range(0,len(cipher),4):
msg += decrypt(cipher[i:i+4],key)
return msg.strip('\x00')
def decrypt(msg,key):
subkeys = unpack('>4H',key)
left,right = unpack('>2H',msg)
left = right^left
for i in range(3):
left,right = right,left
left = left^f(subkeys[2-i]^right)
right = right^subkeys[3]
return pack('>2H', left, right)
def encrypt_ecb(msg,key):
l = len(msg)
if l%4 !=0:
msg = msg+'\x00'*(4-(l%4))
cipher = ''
for i in range(0,len(msg),4):
cipher += encrypt(msg[i:i+4],key)
return cipher
def encrypt(msg,key):
subkeys = unpack('>4H',key)
left,right = unpack('>2H',msg)
right = right^subkeys[3]
for i in range(3):
tmp = left^f(subkeys[i]^right)
left = right
right = tmp
left = right^left
return pack('>2H', left, right)
def dfa_f():
for i in range(1000):
input1 = random.randint(0,0xffff)
output1 = f(input1)
input2 = input1^0x8080
output2 = f(input2)
assert(output1^output2 == 0x400)
def genpayload1(num):
payload = ''
for i in range(num):
data1 = random.randint(0,0xffff)
data2 = random.randint(0,0xffff)
data2diff = data2^0x8080
payload += pack('>2H',data1,data2)
payload += pack('>2H',data1,data2diff)
return payload
def genpayload2(num):
payload = ''
for i in range(num):
data1 = random.randint(0,0xffff)
data2 = random.randint(0,0xffff)
data2diff = data2^0x400
payload += pack('>2H',data1,data2)
payload += pack('>2H',data1,data2diff)
return payload
def testkey_round3(pairs,key):
for pair in pairs:
output1 = pair[0]
output2 = pair[1]
output1_0,output1_1 = unpack('>2H',output1)
output2_0,output2_1 = unpack('>2H',output2)
f_out_diff = output1_1 ^ output2_1 ^0x400
f_in1 = key^output1_0^output1_1
f_in2 = key^output2_0^output2_1
if(f(f_in1)^f(f_in2)==f_out_diff):
continue
else:
return False
return True
def testkey_round2(pairs,key,r3key):
for pair in pairs:
output1 = pair[0]
output2 = pair[1]
output1_0,output1_1 = unpack('>2H',output1)
output2_0,output2_1 = unpack('>2H',output2)
output1_r3_1 = output1_0^output1_1
output2_r3_1 = output2_0^output2_1
f_out_diff = output1_r3_1^output2_r3_1^0x400
f_in1 = key^output1_1^f(r3key^output1_r3_1)
f_in2 = key^output2_1^ f(r3key^output2_r3_1)
if(f(f_in1)^f(f_in2)==f_out_diff):
continue
else:
return False
return True
def attack_round1(msg,cipher,keys):
ciphers = [cipher[i:i+4] for i in range(0,len(cipher),4)]
msgs = [msg[i:i+4] for i in range(0,len(msg),4)]
c = ciphers[0]
m = msgs[0]
output0,output1 = unpack('>2H',c)
output0 = output0^output1
input0,input1 = unpack('>2H',m)
candkeys = []
for key in keys:
r2k,r3k = key
output_r2_1 = output0
output_r2_0 = output1^f(r3k^output0)
output_r1_1 = output_r2_0
output_r1_0 = output_r2_1^f(r2k^output_r2_0)
k0 = output_r1_0^input1
for k in range(0x10000):
f_in = k^output_r1_0
f_out = output_r1_1^input0
if f(f_in) == f_out:
candkeys.append([k,r2k,r3k,k0])
return candkeys
def attack_round2(msg,cipher,keys):
ciphers = [cipher[i:i+4] for i in range(0,len(cipher),4)]
cipher_pairs = [(ciphers[i],ciphers[i+1]) for i in range(0,len(ciphers),2)]
candkeys = []
for r3k in keys:
for key in range(0x10000):
if testkey_round2(cipher_pairs,key,r3k):
candkeys.append([key,r3k])
return candkeys
def attack_round3(msg,cipher):
ciphers = [cipher[i:i+4] for i in range(0,len(cipher),4)]
cipher_pairs = [(ciphers[i],ciphers[i+1]) for i in range(0,len(ciphers),2)]
candkeys = []
for key in range(0x10000):
if testkey_round3(cipher_pairs,key):
candkeys.append(key)
return candkeys
def exploit():
con = remote('127.0.0.1',10005)
context.log_level = 'debug'
con.recvuntil("XXXX+")
d = con.recvuntil(")")[:-1]
con.recvuntil(" == ")
target = con.recvline().strip()
ans = bruteforce(lambda x: sha256(x+d).hexdigest() == target,string.letters+string.digits,4)
con.sendlineafter("Give me XXXX",ans)
con.recvuntil('is:')
flag = con.recvline().strip()
payload = genpayload1(6)+genpayload2(6)
con.sendlineafter(':',payload)
cipher = con.recv(len(payload))
cipher_round3 = cipher[:48]
msg_round3 = payload[:48]
possible_keys = attack_round3(msg_round3,cipher_round3)
print 'round3 keys maybe:', possible_keys
cipher_round2 = cipher[48:96]
msg_round2 = payload[48:96]
possible_keys = attack_round2(msg_round2,cipher_round2,possible_keys)
print 'round2 keys maybe:', possible_keys
possible_keys = attack_round1(msg_round2,cipher_round2,possible_keys)
print 'round1&0 keys maybe:',possible_keys
for key in possible_keys:
real_key = pack('>4H',*key)
print 'decrypt with key ',repr(real_key)
print repr(decrypt_ecb(flag,real_key))
con.close()
def exploit_local():
key = os.urandom(8)
print repr(key)
payload = genpayload1(6)+genpayload2(6)
cipher = encrypt_ecb(payload,key)
cipher_round3 = cipher[:48]
msg_round3 = payload[:48]
possible_keys = attack_round3(msg_round3,cipher_round3)
print 'round3 keys maybe:', possible_keys
cipher_round2 = cipher[48:96]
msg_round2 = payload[48:96]
possible_keys = attack_round2(msg_round2,cipher_round2,possible_keys)
print 'round2 keys maybe:', possible_keys
possible_keys = attack_round1(msg_round2,cipher_round2,possible_keys)
print 'round1&0 keys maybe:',possible_keys
flag = 'flag{test}'
flag = encrypt_ecb(flag,key)
print decrypt_ecb(flag,key)
for key in possible_keys:
real_key = pack('>4H',*key)
print 'decrypt with key ',repr(real_key)
print repr(decrypt_ecb(flag,real_key))
exploit()