看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析

Editor發表於2021-12-15

看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析


今天中午12點整,第十一題《圖窮匕見》截止答題!這也意味著看雪·眾安2021 KCTF秋季賽到此圓滿結束了~


本題僅有2支戰隊成功破解,分別是【辣雞戰隊】和【中婭之戒】。


看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析


恭喜辣雞戰隊用時170067秒拿下“一血”,接下來和我一起來看看該賽題的設計思路和相關解析吧~




出題團隊簡介


第十一題《圖窮匕見》出題方:【98k】戰隊。

看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析



賽題設計思路


怎麼提升逆向的樂趣呢?於是誕生了這個比賽題目。


很多人都從教師或者技術專家的授課中得知些大名鼎鼎的加密演算法,加密演算法的核心邏輯是怎麼在隨機和不隨機中找到一個較好的平衡點。對於SN程式來說,序列號就是加密演算法中的明文或者金鑰。

 

一般而言,設計一個SN演算法,單序列號主要使用加密演算法進行魔改,多序列號主要使用簽名函式進行魔改。當然,很多有樂趣的題目使用各式各樣的數學問題來設計校驗過程。

 

Feistal 結構大家肯定不陌生,其中的靈魂設計在於那個不可逆的F函式,這個題目的靈感就來源於這個F函式。如果這個F函式是一個線性的函式,則會對密碼學演算法產生破壞性的影響。

def pure_block_cipher_encrypt(p: list, k: list, round_n: int, constant_c: list):    assert len(p) == 2    assert len(k) == 2    pl = p[0]    pr = p[1]    tmpl = pl    tmpr = pr    for i in range(round_n):        tmpl, tmpr = pure_block_cipher_enc_round(tmpl, tmpr, k[i % 2], constant_c[i])    return tmpl, tmpr def pure_block_cipher_enc_round(xl, xr, ki, ci):    yl = xr    yr = xl + (xr + ki + ci)^3    return (yl, yr)


程式中為了限制多解,使用了魔改的md5校驗答案,當然,我在設計題目的時候,你對程式進行分析不會產生多解。程式還用了aes的sbox來混淆視聽,加個了沒加一樣,其實是用來干擾基於特徵的分析外掛。

 

以上的程式碼都是sagemath的程式碼。其中“^”符號是指數冪運算的符號,不是 xor 。

 

總是說線性的東西是脆弱的,如果一個加密演算法,無論它應用在什麼上,hash、加密,如果只有線性的運算,沒有非線性的運算,最終它的明文密文會在某個代數結構上構成一組線性方程。

 

本題實現了在 Galois Fields 上的多項式環。

equations.append(x1+pr_list[0])equations.append(x2+pl_list[0]+(k_list[0] + pr_list[0] + constant_c[0])^3) for i in range(1, round_n):    this_round_vars = x_list[i*2-2:i*2+2]    # print(this_round_vars)    equations.append(this_round_vars[1] + this_round_vars[2])    equations.append(this_round_vars[3] + this_round_vars[0] + (k_list[i % 2] + constant_c[i] + this_round_vars[1])^3)equations.append(x_list[-2]+cl_list[0])equations.append(x_list[-1]+cr_list[0])equations.append(y1+pr_list[1])equations.append(y2+pl_list[1]+(k_list[0] + pr_list[1] + constant_c[0])^3) for i in range(1, round_n):    this_round_vars = y_list[i*2-2:i*2+2]    # print(this_round_vars)    equations.append(this_round_vars[1] + this_round_vars[2])    equations.append(this_round_vars[3] + this_round_vars[0] + (k_list[i % 2] + constant_c[i] + this_round_vars[1])^3)equations.append(y_list[-2]+cl_list[1])equations.append(y_list[-1]+cr_list[1])


本演算法是5輪的 Feistal 結構,所以我們定義好中間過程的變數,將它們使用等式連結起來。這些等式在是原先多項式環的子集,滿足原來的加法,自成一群。且該子環內所有元素與原環之元素相乘的結果均在其內。所以可以構建理想。

 

Gröbner basis可以求理想從任意一個多項式理想的一組給定生成元,計算另一組性質良好的生成元。常常用來求解多項式方程。


所以我們在這個理想上求Gröbner basis,即可算出金鑰。算出金鑰後,題目的難點就解決了,剩下的就是滿足輸入規則即可。



賽題解析


本賽題解析由看雪論壇xym給出:

看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析


題目非常小巧,IDA開啟後所有演算法也清晰可見。相比第十題各種逆向方法百花齊放,這題簡單到可以把F5後的結果直接複製出來使用。


題目邏輯也很清晰:將輸入經過一輪s盒轉換後當成金鑰,分別對兩組明文進行加密,如果與指定的兩組密文相同,則將輸入經過hash比較無誤後輸出"Correct!"。整體就是已知兩對明密文和加密演算法,求金鑰的操作。


加密函式sub_140002970也不復雜,梳理一下就是一個Feistal結構的加密,據此容易構造出解密方法。加密輪數只有5輪,定義域在GF(2^64)上。

void sub_140002970(UINT64 *Plaintext, UINT64 *key, int len, UINT64 *const0, UINT64 *Ciphertext){    UINT64 v8; // rax    UINT64 v9; // rax    UINT64 v10; // rax    UINT64 v14; // [rsp+40h] [rbp-38h]    UINT64 v17; // [rsp+58h] [rbp-20h]    UINT64 v18; // [rsp+60h] [rbp-18h]     v17 = Plaintext[0];    v18 = Plaintext[1];    for (int i = 0; i < len; ++i)    {        v8 = v18 ^ key[i % 2] ^ const0[i];        v9 = sub_1400028C0(v8, v8);        v10 = sub_1400028C0(v9, v8);        v14 = v17 ^ v10;        v17 = v18;        v18 = v14;    }    Ciphertext[0] = v17;    Ciphertext[1] = v18;}


其中只有一個sub_1400028C0子函式比較特殊。而這個函式粗一看可能比較奇怪,仔細研究後發現就是一個基於0x247F43CB7的乘法。

UINT64 sub_1400028C0(UINT64 a1, UINT64 a2){    UINT64 v3;    v3 = 0;    while (a2)    {        if ((a2 & 1) != 0)            v3 = v3 ^ a1;        a2 >>= 1;        if (a1 & 0x8000000000000000)            a1 = (2 * a1) ^ 0x247F43CB7;        else            a1 *= 2;    }    return v3;}


把異或看成是加法的話,sub_1400028C0函式完全滿足乘法三大定律。即

sub_1400028C0(a, b) == sub_1400028C0(b, a)sub_1400028C0(sub_1400028C0(a, b), c) == sub_1400028C0(a, sub_1400028C0(b, c))sub_1400028C0(a xor b, c) == sub_1400028C0(a, c) xor sub_1400028C0(b, c))


因此可以進一步把加密過程的每一輪都化簡為如下形式:

v14 = (P1 + KEY0 + const0) ^ 3 + P0;P0 = P1;P1 = v14;...


如果把加密過程的每一輪都表示成如上形式後,可以發現密文在理論上可以表示為明文的表示式,而且裡面只有KEY0和KEY1兩個未知變元,因此利用兩組明密文列出的兩個方程在理論上可以求解得到祕鑰。


使用sagemath實現題目的過程,如下:

def enc(pt, key):  pt0, pt1 = pt  key0, key1 = key  var('x')  var('a')  K.<a> = GF(2^64, name='a',modulus = 1 +x^1 +x^2 +x^4 +x^5 +x^7 +x^10 +x^11 +x^12 +x^13 +x^18 +x^20 +x^21 +x^22 +x^23 +x^24 +x^25 +x^26 +x^30 +x^33 + x^64)  c0=0x20F4641148FA59A5  c1=0x96950F0D368EB90D  c2=0x7467551A8419E0B5  c3=0x01A61218FE968D86  c4=0x192DB7EB78969270  cntList=[]  cntList.append(K.fetch_int(c0))  cntList.append(K.fetch_int(c1))  cntList.append(K.fetch_int(c2))  cntList.append(K.fetch_int(c3))  cntList.append(K.fetch_int(c4))  Left = K.fetch_int(pt0)  Right = K.fetch_int(pt1)  keyList=[]  keyList.append(K.fetch_int(key0))  keyList.append(K.fetch_int(key1))  for i in range(5):    tmp = Right+keyList[i%2]+cntList[i]    tmp = tmp^3    tmp = tmp + Left    Left = Right    Right = tmp  return [hex(Left.integer_representation()),hex(Right.integer_representation())]pt=[0x73C51960EF361B30, 0xFBD5C824382C435D]key = [0xb118c960bcb118c9, 0xb118c960bcb118c9]enc(pt,key)


直接構造方程的思路比較簡單,但是方程次數指數級增長導致難以求解。考慮到儘量降低方程次數,編寫相應的解密函式,採用中間相遇攻擊方法,利用在不同輪數中的相遇情況,可以列出6個方程,然後使用Groebner基方法即可求解出方程組的解。


具體過程如下:

#下面程式碼可以直接在sage中執行 #1. 構造數域K.<x> = GF(2^64, name='x',modulus = 1 +x^1 +x^2 +x^4 +x^5 +x^7 +x^10 +x^11 +x^12 +x^13 +x^18 +x^20 +x^21 +x^22 +x^23 +x^24 +x^25 +x^26 +x^30 +x^33 + x^64)#2. 方程的構造#首先形式化計算出表示方法P0=var('P0')P1=var('P1')M0=var('M0')M1=var('M1')y = var('y')z = var('z')c0 = var('c0')c1 = var('c1')c2 = var('c2')c3 = var('c3')c4 = var('c4')R.<P0,P1,y,z,c0,c1,c2,c3,c4,M0,M1>=GF(2)[]#正向加密過程形式化計算v17 = P0v18 = P1 v8 = v18 + y + c0v10 = v8 * v8 * v8v14 = v17 + v10v17 = v18v18 = v14 eq_forward_1 = v18 v8 = v18 + z + c1v10 = v8 * v8 * v8v14 = v17 + v10v17 = v18v18 = v14 eq_forward_2 = v18 v8 = v18 + y + c2v10 = v8 * v8 * v8v14 = v17 + v10v17 = v18v18 = v14 eq_forward_3 = v18#逆向解密過程形式化計算v17 = M1v18 = M0 v8 = v18 + y + c4v10 = v8 * v8 * v8v14 = v17 + v10v17 = v18v18 = v14 eq_backward_1 = v18 v8 = v18 + z + c3v10 = v8 * v8 * v8v14 = v17 + v10v17 = v18v18 = v14 eq_backward_2 = v18 v8 = v18 + y + c2v10 = v8 * v8 * v8v14 = v17 + v10v17 = v18v18 = v14 eq_backward_3 = v18 #其次帶入實際資料 ct0 = 0x55250D8CEEDFFB35ct1 = 0xDBAFC899D2AAD5ECct2 = 0xD30CE81D5CFD183Ect3 = 0x3C205341A484C650pt0 = 0x73C51960EF361B30pt1 = 0xFBD5C824382C435Dpt2 = 0x79EEBA9CCF47A451pt3 = 0x55B4D0E1061D12D0cnt0 = 0x20F4641148FA59A5cnt1 = 0x96950F0D368EB90Dcnt2 = 0x7467551A8419E0B5cnt3 = 0x01A61218FE968D86cnt4 = 0x192DB7EB78969270 y,z=K['y,z'].gens() #利用第1組明密文對列3個方程eq1 = \eq_forward_1(P0=K.fetch_int(pt0),P1=K.fetch_int(pt1),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4))\-eq_backward_3(M0=K.fetch_int(ct0),M1=K.fetch_int(ct1),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4)) eq2 = \eq_forward_2(P0=K.fetch_int(pt0),P1=K.fetch_int(pt1),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4))\-eq_backward_2(M0=K.fetch_int(ct0),M1=K.fetch_int(ct1),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4)) eq3 = \eq_forward_3(P0=K.fetch_int(pt0),P1=K.fetch_int(pt1),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4))\-eq_backward_1(M0=K.fetch_int(ct0),M1=K.fetch_int(ct1),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4)) #利用第2組明密文對列3個方程eq4 = \eq_forward_1(P0=K.fetch_int(pt2),P1=K.fetch_int(pt3),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4))\-eq_backward_3(M0=K.fetch_int(ct2),M1=K.fetch_int(ct3),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4)) eq5 = \eq_forward_2(P0=K.fetch_int(pt2),P1=K.fetch_int(pt3),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4))\-eq_backward_2(M0=K.fetch_int(ct2),M1=K.fetch_int(ct3),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4)) eq6 = \eq_forward_3(P0=K.fetch_int(pt2),P1=K.fetch_int(pt3),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4))\-eq_backward_1(M0=K.fetch_int(ct2),M1=K.fetch_int(ct3),c0=K.fetch_int(cnt0),\c1=K.fetch_int(cnt1),c2=K.fetch_int(cnt2),c3=K.fetch_int(cnt3),c4=K.fetch_int(cnt4)) #至此列出6個方程。 #3.利用sagemath自帶的Groebner基解方程I=ideal(eq1,eq2,eq3,eq4,eq5,eq6)B=I.groebner_basis()key0 = hex(B[0].coefficients()[1].integer_representation())key1 = hex(B[1].coefficients()[1].integer_representation())print(key0,key1)


由於上述方程只有唯一解0xf39a46cc6199261d, 0xbdeded27481af0e0,所以該解經過s盒的逆變換後變化得到的字串de23f9d82798377ea01743d43d5353cd直接通過了後面部分的HASH校驗。


看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析



注意:【最受歡迎戰隊獎】投票進行中!歡迎大家前來投上你寶貴的一票~


看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析

長按二維碼立即投票!

看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析

【最受歡迎戰隊獎】


評選物件:

2021 KCTF秋季賽 參賽戰隊(包含防守方所有戰隊+攻擊方前20名戰隊)


評選方式:

登陸看雪賬號,在本帖為喜歡的戰隊投票,每個賬號可投1票。


投票時間:

12月15日 14:00 ~12月22日 14:00


獎勵:

最受歡迎戰隊獎 ,得票第一名:可獲得「HUAWEI WATCH GT2 華為手錶」

看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析



看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析


往期解析


1、看雪·眾安 2021 KCTF 秋季賽 | 第二題設計思路及解析


2、看雪·眾安 2021 KCTF 秋季賽 | 第三題設計思路及解析


3、看雪·眾安 2021 KCTF 秋季賽 | 第四題設計思路及解析


4、看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析


5、看雪·眾安 2021 KCTF 秋季賽 | 第六題設計思路及解析


6、看雪·眾安 2021 KCTF 秋季賽 | 第七題設計思路及解析


7、看雪·眾安 2021 KCTF 秋季賽 | 第八題設計思路及解析


8、看雪·眾安 2021 KCTF 秋季賽 | 第九題設計思路及解析



看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析

- End -


看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析

公眾號ID:ikanxue

官方微博:看雪安全

商務合作:wsc@kanxue.com

相關文章