看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路

Editor發表於2019-07-05


“池塘邊的榕樹上,知了在聲聲叫著夏天~”伴著窗外綠葉間的蟬鳴,我們迎來週五的夕陽,於此同時,我們看雪紐盾KCTF的Q2賽題解析也到達尾聲。


此刻,小夥伴們有沒有一丟丟不捨和眷戀呢?沒關係,我們的KCTF第三賽段在九月火熱開啟,等你來戰!


今天我們來看下第十題,觀英雄鬥智鬥勇,開啟時間之輪,逆轉乾坤~


題目簡介


題目背景:


歷經千辛萬苦,集齊所有的能量寶石,但成功開啟時間之輪,開啟進入另一個平行時空的入口,還差一步。


你需要將這八顆寶石按照正確的順序排列出來,才能夠成功開啟時間之輪,否則能量寶石也只是八塊石頭……


快來尋找寶石排列的祕密吧!人類的命運如今就掌握在你的手中。


看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路


本題共有1990人圍觀,截至比賽結束只有5人攻破此題。縱觀全域性,十道題目中難度排名前三,難倒了不少英雄好漢。


攻破此題的戰隊一覽:


看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路


接下來我們來對題目進行詳細解析。


看雪評委crownless點評



本題演算法基於二次剩餘與離散對數,主要涉及公鑰密碼學的知識。解決此題的關鍵在於,不僅要掌握紮實的密碼學、數學知識,而且還要懂逆向分析、程式分析。



出題團隊簡介



本題出題戰隊 ReadPage :


看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路

看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路


論壇ID為readyu,畢業於中國科學技術大學自動控制專業,從事軟體開發多年。在軟體保護技術、加密演算法方面有一些體驗。曾在北京多看科技從事電子閱讀加密技術的研究,以及在MIUI從事系統安全方面工作。



設計思路



本題演算法主要涉及公鑰密碼學的基礎知識。Win32程式, 無殼, 無Anti。暫定取名 “一石二鳥”。



一、序列號

本題唯一序列號SN為:


KCTFREADYKXXXX1548396171915056368526513804948765619094392315806578461796159505215278288254



二、方程

演算法基於二次剩餘與離散對數, 建立了2個方程 (1) (2)  見下文,並且模同一個素數,所以暫定取名 “一石二鳥”。


P=2^255-19 是一個素數,base16 或者base10如下:


P(hex)=7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED


P(dec)=57896044618658097711785492504343953926634992332820282019728792003956564819949

序列號格式為兩段字元, + 表示字串合併:


SN = “s1” + “XXXX” + “s2”

s1是大寫字母串, s2是十進位制數字串。數值上, 1 < s1,  s2 < P

其中,s1,s2 是未知量, d是s1變換而來的未知量。

s1逆序變換,加上最小位調整, 然後base25解碼得到d. (Y,G)內建於程式之中。

64*(s2-s1)^4 + (s2-s1)^2 + 3 = s1 mod P    ... (1)

G^d = Y  mod P      ... (2) 


G,Y 是已知數, 範圍 [ 1, P-1]:

Y = 100

G = 9230197858975018299629857977411527954550899478307510809210520967346958600039 



三、解法


解法:首先解方程2,得到d,轉換為s1 ;然後再解方程1,得到s2。


3.1 解方程(2)


方程(2)解釋如下:
d = base25.reverse(s1) ....(3)

隨機驗證任何一對,結果都是一樣的。


(Y,G) =

        "100,9230197858975018299629857977411527954550899478307510809210520967346958600039",
        "101,50414221767352083765613498524674590844333823720255656432490557866777248860034",
        "102,38377684164112914669201831650756813551072223314592288217929947158283532270268",
        "103,13436195533519778671648120865743178010431697022400670384909515001970400645091",

比如 Y=100,
     G=9230197858975018299629857977411527954550899478307510809210520967346958600039

G^d = Y mod P ,  P是256bit的大素數。

表面一看,需要解256 bit的離散對數, 需要解離散對數工具,比如gdlog。


GDLOG
Implementation of the GNFS for discrete logarithm problem in GF(p) 
https://sourceforge.net/projects/gdlog/

但本題,程式裡給出了條件,d 轉化為base25字元時,最長為10個位元組。 


所以數量級極大地縮小,只有47bits。也就是d < limit, limit = 25^10 = 95367431640625 。


因此,用pollard kangaroo 演算法求解d,範圍為band = (1, 95367431640625) 只有47 bits。


kangaroo演算法是解決區間band = (a, b)上離散對數問題很有效的方法,在平均意義下需要進行2 * √|a-b|次群操作。


期望步數 2 *(limit^0.5)= 2* 9765625, 也就是大概2000萬次。經測試, 大概30-60秒之間。

我們得到:

d(dec) = 79821823136933
d(hex)= 4898F769D4A5 
base25table=ABCDEFGHKJILMNOPQRSTUVWYZ  


是26個大寫字母表,(扣除X, 並且K,I互換位置),得到 d.base25 = UYDAERFTCK

逆序後得到:s = d.base25.reverse = KCTFREADYU


為了防止被猜測到s1, 低位做了修正,修正值 diff = 'U' - 'K' = 10:


註冊碼裡的採用KCTFREADYK, 
s1 = KCTFREADYK + diff = KCTFREADYU
s1(hex) = 4B435446524541445955

3.2 解方程(1)


1看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路

首先,根據一般的二次剩餘方程, 可以配方以後求解:
a*x^2 + bx + c = 0  mod p


兩邊乘以4a配方:
4a^2*x^2 + 4a*bx + 4ac = 0  mod p

方程變為
(2a*x + b)^2 = b^2 - 4ac


令delta = b^2 - 4ac, 就可以歸結為求  sqroot(delta) mod p:

x1 = (-b - sqroot(delta))/(2a)   mod p

x2 = (-b + sqroot(delta))/(2a)   mod p


注意,這裡的除法是模P的逆。

2看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路

方程(1)解釋如下:

64*(s2 - s1)^4 +  (s2-s1)^2 + 3 - s1 = 0 mod P

F(x)是一個4次多項式:F(x) = a4*x^4 + a2*x^2 + a0

這裡取x = s2 - s1, a4 = 64, a2 = 1, a0 = 3

F(x) 可以簡化為一個二次剩餘方程,


64*(s2 - s1)^4 +  (s2-s1)^2 + 3 = s1  mod P  ... (4)

64*r^2 + r + 3 = s1 mod p , 
=> 64*r^2 + r  = (s1 - 3) mod p 

兩邊乘以4a,加上1:
4*64*64*r^2 + 256*r + 1 = 256*(s1-3) + 1 mod p

(128r + 1)^2 = 256*(s1-3) + 1  mod p

s1 = 4B435446524541445955, s1-3 = 4B435446524541445952


乘以256就是左移一個位元組。


3看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路


方程簡化為:

(128r + 1)^2 = 4B43544652454144595201  mod p  ...(5)
r =  (sqroot(4B43544652454144595201, P) - 1)/128

第一步,方程(5)求解首先求得兩個解r1,r2。

Y = 4B43544652454144595201
sqroot(Y, P), RDLP 求解。

Two sqroots of Y (mod P), in HEX BASE.


G1-258B783A22015B08A6C64FB55644BAACCDA201473D4B6786821056707C680B58
G2-5A7487C5DDFEA4F75939B04AA9BB4553325DFEB8C2B498797DEFA98F8397F495

再求得:
r1=2D4B16F0744402B6114D8C9F6AAC8975599B44028E7A96CF0D0420ACE0F8D010
r2=1CB4E90F8BBBFD49EEB273609553768AA664BBFD71856930F2FBDF531F072FE5  

然後r1,r2分別求解二次剩餘,各有兩個解,所以一共有4個解。

第二步,解出 x^2 = r mod p,可用RDLP求解。


root(r1)=  
x2-3CCA260F45B79993C67F35F7A716B28BBA591BA35593C8DEB9B959C2CE43AE21
x3-4335D9F0BA48666C3980CA0858E94D7445A6E45CAA6C37214646A63D31BC51CC

root(r2)=
x4-7C93A389F44E31BC25D90165624292389B47C2C27F60286FF627A6FC3DB84BC4
x1-036C5C760BB1CE43DA26FE9A9DBD6DC764B83D3D809FD79009D85903C247B429

這4個解記作x1,x2,x3,x4, 恰好每一個都分佈在(1, P)的4個區間之一。
(0-1/4), (1/4-1/2), (1/2 - 3/4), (3/4, 1)

然後:s2 = s1 + X  , 也有4個s2, 並轉為10進製表示。


最後合併:
SN = "s2" + "XXXX" + "s1"

4個解為:

SN_X1=KCTFREADYKXXXX1548396171915056368526513804948765619094392315806578461796159505215278288254


SN_X2=KCTFREADYKXXXX27495936700183671733408543181646240981077460232127048216208422649817010276214


SN_X3=KCTFREADYKXXXX30400107918474425978376949322697712945557532100693234514359350753673330338593


SN_X4=KCTFREADYKXXXX56347648446743041343258978699395188307540600017013704268771613898275062326553

題目取最小的解:

x < P/4,SN_X1 為有效答案。



解題思路



本題解題思路由看雪論壇ODPan提供:


看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路


這是一道數論高配題。整數要用大整數,方程要用模方程,模方程要用二次模方程,二次模方程要用二次模合數方程,求逆要選離散對數。


該題選用了mbedtls大數庫,可以對應著原始碼檢視分析。



程式的初步分析


作者一如既往的對話方塊程式,我們需要關心兩個程式。sub_403B00 輸入字元處理函式和sub_404270校驗函式。校驗函式做了抗F5小處理。將此處Nop掉即可F5分析。


.text:00404B1E    pop     ebp



輸入字元處理


簡單檢視了一下。合法字串使用“XXXX”進行分割,分為前後兩個部分,並轉化為string型別。


char__cdeclstrProcess(char*key, ctf10_Str *strRetKeyBefore, ctf10_Str *strRetKeyAfter)
{
char*pchar;// eax
unsigned__int64 XXXXlen;// ST04_8
char*index;// eax
char*index_1;// esi
ctf10_Str *strKeyBefore;// eax
ctf10_Str *strTemp;// eax
unsignedintkeyAfterLen;// ecx
_BYTE *pchar_keyAfter;// eax
intv11;// edi
char*pchar_keyAfter_1;// eax
charv13;// al
charv14;// al
charv15;// al
charresult;// al
charv17;// al
ctf10_Str strXXXX;// [esp+Ch] [ebp-3Ch]
ctf10_Str strKey;// [esp+1Ch] [ebp-2Ch]
ctf10_Str strKeyAfter;// [esp+2Ch] [ebp-1Ch]
intv21;// [esp+44h] [ebp-4h]

LOBYTE(strKey.field_0) = (_BYTE)key;
StrInitByFalg(&strKey,0);
strLoad(&strKey, key,strlen(key));
LOBYTE(strXXXX.field_0) = (_BYTE)key;
v21 =0;
StrInitByFalg(&strXXXX,0);
strLoadByInput(&strXXXX,0,4u,'X');
pchar = (char*)strXXXX.pchar;
LOBYTE(v21) =1;
if( !strXXXX.pchar )
pchar = (char*)&unk_4100FC;
HIDWORD(XXXXlen) = strXXXX.len;
LODWORD(XXXXlen) =0;
index = str_find(&strKey, pchar, XXXXlen);// find XXXX
index_1 = index;
if( index == (char*)-1)
gotoLABEL_39;
strKeyBefore = str_substr(&strKey, &strKeyAfter,0, (unsignedint)index);
LOBYTE(v21) =2;
strCpy(strRetKeyBefore, strKeyBefore,0,0xFFFFFFFF);
LOBYTE(v21) =1;
StrInitByFalg(&strKeyAfter,1);
strTemp = str_substr(&strKey, &strKeyAfter, (unsignedint)&index_1[strXXXX.len],0xFFFFFFFF);
LOBYTE(v21) =3;
strCpy(strRetKeyAfter, strTemp,0,0xFFFFFFFF);
LOBYTE(v21) =1;
StrInitByFalg(&strKeyAfter,1);
if( !strRetKeyBefore->len )
gotoLABEL_39;
keyAfterLen = strRetKeyAfter->len;
if( !keyAfterLen )
gotoLABEL_39;
pchar_keyAfter = (_BYTE *)strRetKeyAfter->pchar;
if( !pchar_keyAfter )
pchar_keyAfter = &unk_4100FC;
if( *pchar_keyAfter =='0')
{
LABEL_39:
LOBYTE(v21) =0;
StrInitByFalg(&strXXXX,1);
v21 =-1;
StrInitByFalg(&strKey,1);
return0;
}
v11 =0;
if( keyAfterLen >0)
{
while(1)
{
pchar_keyAfter_1 = (char*)strRetKeyAfter->pchar;
if( !pchar_keyAfter_1 )
pchar_keyAfter_1 = (char*)&unk_4100FC;
if( !isdigit(pchar_keyAfter_1[v11]) )// 數字
break;
if( (unsignedint)++v11 >= strRetKeyAfter->len )
gotoLABEL_14;
}
if( strXXXX.pchar )
{
v14 = *(_BYTE *)(strXXXX.pchar -1);
if( v14 && v14 !=-1)
*(_BYTE *)(strXXXX.pchar -1) = v14 -1;
else
strFree((LPVOID)(strXXXX.pchar -1));
}
strXXXX.pchar =0;
strXXXX.len =0;
strXXXX.field_C =0;
if( strKey.pchar )
{
v15 = *(_BYTE *)(strKey.pchar -1);
if( v15 && v15 !=-1)
{
*(_BYTE *)(strKey.pchar -1) = v15 -1;
result =0;
}
else
{
strFree((LPVOID)(strKey.pchar -1));
result =0;
}
returnresult;
}
return0;
}
LABEL_14:
if( strXXXX.pchar )// 全是數字的處理
{
v13 = *(_BYTE *)(strXXXX.pchar -1);
if( v13 && v13 !=-1)
*(_BYTE *)(strXXXX.pchar -1) = v13 -1;
else
strFree((LPVOID)(strXXXX.pchar -1));
}
strXXXX.pchar =0;
strXXXX.len =0;
strXXXX.field_C =0;
if( strKey.pchar )
{
v17 = *(_BYTE *)(strKey.pchar -1);
if( v17 && v17 !=-1)
{
*(_BYTE *)(strKey.pchar -1) = v17 -1;
return1;
}
strFree((LPVOID)(strKey.pchar -1));
}
return1;
}



校驗函式


該函式要滿足3個條件,才能返回正確結果。下面分別分析這三個條件。


函式初始化部分


大整數初始化


mbedtls_mpi_lset(&power0xff, v91);
BigInt_pow(&ret, &power0xff, v7);// 2^0xff
mbedtls_mpi_add_int(&power0xff, &ret, v8);// 2^0XFF - 0X13
mbedtls_mpi_sub_int(&powerSbu1, &power0xff,1);// /2^0XFF - 0X13 - 1
mbedtls_mpi_lset(&A, a4);


將輸入字串的兩部分轉化為大整數,我們假設a=keyBefore,b= keyAfter 


mbedtls_mpi_copy(&keyBefore, &mytarget);
mbedtls_mpi_copy(&keyAfter, &srcNode);


第一個條件:4b < power0xff


mbedtls_mpi_add_mpi(&doubleAfter, &keyAfter, &keyAfter);
mbedtls_mpi_add_mpi(&node4b, &doubleAfter, &doubleAfter);
v12 = mbedtls_mpi_cmp_mpi(&node4b, &power0xff);// 4b < power0xff


第一個條件是個限制條件,4*b < 2^0xff – 0x13。


第二個條件:模方程


mbedtls_mpi_lset(&sum, v12 >=0);// v90 = 0
v13 =0;
v63 =0;
v14 = v50;
while( v13 < v14 )// v14= 6
{
mbedtls_mpi_sub_mpi(&afterSunBefor, &keyAfter, &keyBefore); r=b-a
powern(&desNode[v13], &afterSunBefor, *(&n + v13), &power0xff);// n=0,1,2,3,4,5
addNum(&v58, *(&num + v13), &desNode[v13], &sum, &power0xff);// a*num+v90 num = 3,0,1,0,0x40,0,1
mbedtls_mpi_copy(&sum, &v58);
v63 = ++v13;
}
mbedtls_mpi_sub_mpi(&a1a, &keyBefore, &sum);


這裡我們另r=b-a,迴圈6次的過程如下:

sum0 =  r^0/N                 *3 =3

sum1 =  r^1*0/N              +sum0/N = 3

sum2 =  r^2*1/N              +sum1/N = (r^2+3)/N

sum3 =  r^3*0/N              +sum2/N = (r^2+3)/N

sum4 =  r^4*0x40/N +sum3/N = 0X40r^4/N + (r^2+3)/N

sum5 =  r^5*0/N +sum4


最後得到的方程:

(64r^4/N + (r^2 +3)/N)/n = a


這裡我們另x=r^2,這裡往回逆的時候要逆兩次。化簡後可得一個二次模方程:

64x^2 + x a = 0(mod N)


從方程可知,求出a即可反推x,r最後求得b。那麼我們繼續分析,看如何求a值。


第三個條件:離散對數


mbedtls_mpi_lset(&E,0);
maxTableData = getDataFormTable((char*)'X');// v15 = 0x19
maxTableData_1 = maxTableData;
maxTableData_2 = maxTableData;
index =0;
index_1 =0;
const0xA = *((_DWORD *)checkData +4);
buf =0;
memset(&v39,0,252u);
v40 =0;
v41 =0;
size = *((_DWORD *)checkData +1);
pcharend = (char*)(size + *(_DWORD *)checkData -1);
v36 = (char*)(size + *(_DWORD *)checkData -1);
pbuf = &buf;
v42 = &buf;
while( (unsignedint)pcharend >= *(_DWORD *)checkData )
{
*pbuf++ = *pcharend;
v42 = pbuf;
v36 = --pcharend;
}
buf += checkData[12];// 輸入字串前半部分keyBefore,最後一個字元+0xA
while( index <strlen(&buf) )
{
keyChar1 = (char*)*(&buf + index++);
index_1 = index;
numFromTableByKey = getDataFormTable(keyChar1);
numFromTableByKey_1 = numFromTableByKey;
v43 = numFromTableByKey;
if( numFromTableByKey >= maxTableData_1 )
{
errorFlag =1;
break;
}
mbedtls_mpi_mul_int(&E, &E, maxTableData_1);
mbedtls_mpi_add_int(&E, &E, numFromTableByKey_1);// 將keyBfore內容轉換為0x19進位制數
}
if( index <= const0xA && !errorFlag )// KEY 前半部分長度小於10
{
sushuCnt = createSuShuByRand(randNum, &buf2);// 產生素數列表和隨機檢測個數
randNum = sushuCnt;
for( j =0; ; ++j )// 米勒羅賓素性測試
{
v63 = j;
if( j >= sushuCnt || !*(&buf2 + j) )
break;
sub_4025A0(&ret_1, &E, *(&buf2 + j));
if( !ret_1 )
gotoLABEL_35;
}
get_gccd(&v58, &powerSbu1, &E);// gcd
if( node_getBitNum_0(&v58) <=1)
{
mbedtls_mpi_inv_mod(&D, &E, &powerSbu1);// D = E^-1 mod (N-1)
mbedtls_mpi_exp_mod(&final, &A, &D, &power0xff, &a5);// X = A^D mod N
mbedtls_mpi_sub_mpi(&v71, &nodeCheck, &final);
v25 = node_getData(&v71);
result3 = v25;
mbedtls_mpi_exp_mod(&a2, &nodeCheck, &E, &power0xff, &a5);// A = X^E mode N
mbedtls_mpi_sub_mpi(&v69, &a2, &A);
if( v25 )
result3 = node_getData(&v69);
}


程式碼幾個關鍵步驟說明:

1、 將keyBefore(輸入字串得前半部分)最後一位+0x0A;

2、 將keyBfore內容轉換為0x19進位制數,記作E;

3、 米勒羅賓素性測試,確保E為素數;

4、 E與(N-1)最大公約數為1

5、 檢測計算

D = E^-1 mod (N-1)

X = A^D mod N

A = X^E mode N


N=0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED


這裡我們已知A和X得四組資料,求D,求離散對數。利用作者dlp工具可以跑出D。這樣我們可以一路反推D->E->keyBefore->a,將a帶回二次模方程,這裡簡單提一下方程化簡過程:

64r^4 + r^2 + 3-0x4B435446524541445955 = 0 mod N

64x^2 + x + 3-0x4B435446524541445955 = 0 mod N


轉換為一般模方程:

x^2 = A mode M      

A是奇數非素數,M是個合數  GCD(A,M)==1


其中

a = 64

b = 1

c =-0x4B435446524541445952


所以

A = b^2 - 4ac

M = 4aN


這樣經過兩次二次模方程求解可以解的r(b-a,b<4N。最終求得a,b。


aXXXXb= KCTFREADYKXXXX1548396171915056368526513804948765619094392315806578461796159505215278288254



END


主辦方


看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路

看雪學院(www.kanxue.com)是一個專注於PC、移動、智慧裝置安全研究及逆向工程的開發者社群!建立於2000年,歷經19年的發展,受到業內的廣泛認同,在行業中樹立了令人尊敬的專業形象。平臺為會員提供安全知識的線上課程教學,同時為企業提供智慧裝置安全相關產品和服務。 



合作伙伴



看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路

上海紐盾科技股份有限公司(www.newdon.net)成立於2009年,是一家以“網路安全”為主軸,以“科技源自生活,紐盾服務社會”為核心經營理念,以網路安全產品的研發、生產、銷售、售後服務與相關安全服務為一體的專業安全公司,致力於為數字化時代背景下的使用者提供安全產品、安全服務以及等級保護等安全解決方案。


天上真的掉餡餅啦!     

面試跳槽沒有拿得出手的證照?

升職加薪能力不夠?

公司申請安全資質,但員工要求達不到?

一張CISP的證照,幫您解決全部問題。


預算不夠又想找靠譜的機構?

紐盾幫您解決全部問題,全新的上課機制,豐富的課程安排,超多大型專案供你累積經驗,通過率高達99%。

現報名還有優惠補貼,快來了解下吧。


看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路



看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路


10大議題正式公佈!第三屆看雪安全開發者峰會重磅來襲!


看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路

看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路小手一戳,瞭解更多



相關文章