2021天翼杯 密码官方wp
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()總結
以上是生活随笔為你收集整理的2021天翼杯 密码官方wp的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021-09-25
- 下一篇: 2021-09-26