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

Editor發表於2019-10-08
稷下三賢者培養了許多極具天分的弟子,孫臏是其中佼佼者之一。這個天才少年將兵法與機關術巧妙地結合在一起。
孫臏的成果令師兄龐涓感到威脅,甚至當眾詆譭他。
一次,孫臏在調查遺蹟的途中遭遇狂暴的魔種。被追趕時摔下山崖,埋入一片廢墟中。田忌發現好友失蹤闖入廢墟,試圖解救孫臏。不幸的是,觸動機關,田忌被捲入黑暗的漩渦。
造成事故的巧合並非偶然。龐涓精心策劃的完美報復實現了。後來孫臏被救出,保住了生命,代價則是雙腿和好友田忌。
田忌,一定還在某個地方活著,孫臏堅信。就像噩夢的夜晚,田忌焦急呼喚著他名字一樣,現在輪到他以機關術的力量,重新開啟無邊無垠的時空之門,將最好的朋友帶回家。

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


題目簡介



本題共有1008人圍觀,最終只有13支團隊攻破成功。第一名LookLook戰隊耗時約16個小時,成功破解此題。看似簡單的題目,實際上包含出題人精心策劃的巧妙演算法。


攻破此題的戰隊排名一覽:


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

接下來我們一起來看一下這道題的點評和詳細解析吧。


看雪評委crownless點評



該題目假裝是一個基於ECC橢圓加密求解的題目,如果按照ECC解法做反而是做不出來的。作者還實現了一個很簡單的反除錯殼。



出題團隊簡介



本題出題戰隊iret:


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


下面是團隊簡介:

某安全公司實習生,目前正在學習逆向和木馬分析,希望能從各位大佬身上學習並提升自己。



設計思路



1、該題目假裝是一個基於ECC橢圓加密求解的題目。之前發了一篇ECC橢圓加密的原理分析:https://bbs.pediy.com/thread-253672.htm,相信誤導了不少解題朋友,在此給大家道歉,如果按照ECC解法做反而是做不出來的,具體解法在原始碼中。

2、題目說明:

如果按照ECC解法:

根據橢圓曲線的基本概念,首先給出了橢圓曲線Ep:Ep = y^2 = x^3 + 125*x 以及關鍵的引數:素域p=127,基點G=(11,4) 公鑰K=(120,41)。
私鑰在程式碼和新增的區段中都有說明,沒有給出直接提示:

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


私鑰在原始碼中的體現:

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


一個簡單的異或可以得到x=9。

而根據橢圓曲線定義:

公鑰K = 基點G * 私鑰k

所以私鑰=x=9。

但實際上的變換操作為,加密函式的原始碼:

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

未做混淆,攻擊方可以直接通過閱讀IDA獲取。

程式碼手動寫了一個超級簡單的反除錯殼。

首先通過010Editor給程式新增加了一個區段,區段名順便提示了私鑰:

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

然後把程式原OEP更改到了新增的區段Offset處:

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


使用x32dbg後,使用了:

mov byte ptr ss:[ebp-5],0
mov eax, dword ptr fs:[0x30]
mov al, byte ptr [eax + 2]
mov byte ptr [ebp - 5], al
movzx eax, byte ptr [ebp - 5]

幾行簡單的程式碼判斷是否處於除錯狀態。

如果處於除錯狀態,則設定兩個迴圈跳轉使得程式sleep。

如果未處於除錯狀態,則jmp到程式真實的OEP。

這裡手動將一部分實際應用到的彙編程式碼拷貝了過來,起到一點干擾作用。

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

輸入判斷:

最開始將使用者輸入的字串傳入的加密函式ECode得到了一個加密的字串,將加密字串的ASCII列印出來如下:

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

儲存該字串的ASCII:

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

然後將使用者輸入後的字串加密後與該字串進行比較:

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

最後刪除解密函式以及多餘的列印資訊build成exe進行手動加殼。


解題思路



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


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


初步分析程式


start有一處PEB(02:BeingDebugged)反除錯,直接跳過。程式有混淆,但可以帶著分析程式。

k_is_9:0042F000 start           proc near
k_is_9:0042F000                 mov     byte ptr [ebp-5], 0
k_is_9:0042F004                 mov     eax, large fs:30h
k_is_9:0042F00A                 mov     al, [eax+2]
k_is_9:0042F00D                 mov     [ebp-5], al
k_is_9:0042F010                 movzx   eax, byte ptr [ebp-5]
k_is_9:0042F014                 test    eax, eax
k_is_9:0042F016
k_is_9:0042F016 loc_42F016: ; CODE XREF: start:loc_42F08A↓j
k_is_9:0042F016                 jnz     short loc_42F08A
k_is_9:0042F018                 jmp     sub_4110FF

程式的關鍵函式,根據介面提示搜尋字串直接定位。流程很明確獲取輸入,生成key,和目標字串進行比較檢測。

int __usercall keyFunc@<eax>(int a1@<xmm0>)
{
  int v1; // edx
  int v2; // eax
  int v3; // eax
  int v4; // ecx
  int v5; // eax
  int v6; // eax
  int v7; // ecx
  int v8; // eax
  int v9; // eax
  int v10; // ecx
  _DWORD *v11; // eax
  int v12; // eax
  int v13; // ecx
  void *v14; // edx
  int v16; // [esp-1Ch] [ebp-1F8h]
  int v17; // [esp-8h] [ebp-1E4h]
  void *v18; // [esp-4h] [ebp-1E0h]
  char v19; // [esp+0h] [ebp-1DCh]
  struc_String *keyString_1; // [esp+10h] [ebp-1CCh]
  struc_String *keyString; // [esp+14h] [ebp-1C8h]
  int v22; // [esp+18h] [ebp-1C4h]
  char goalBuf[56]; // [esp+20h] [ebp-1BCh]
  char v24; // [esp+58h] [ebp-184h]
  int v25; // [esp+60h] [ebp-17Ch]
  char v26; // [esp+6Ch] [ebp-170h]
  char v27; // [esp+7Fh] [ebp-15Dh]
  struc_String sting; // [esp+88h] [ebp-154h]
  int *v29; // [esp+ACh] [ebp-130h]
  int v30; // [esp+B8h] [ebp-124h]
  int v31; // [esp+BCh] [ebp-120h]
  struc_String goalString; // [esp+188h] [ebp-54h]
  struc_String keyStringFromInput; // [esp+1ACh] [ebp-30h]
  int v34; // [esp+1CCh] [ebp-10h]
  int v35; // [esp+1D8h] [ebp-4h]
  int savedregs; // [esp+1DCh] [ebp+0h]
 
  G1.x = 11;
  G1.y = 4;
  v30 = j_timesPiont((struc_Point *)a1, &G1, times);
  v31 = v1;
  G2.x = v30;
  G2.y = v1;
  sub_4113B6((int)&keyStringFromInput, a1, byte_4252C2);
  v35 = 0;
  v2 = sub_411802(std::cout, "Ep = y^2 = x^3 + 125*x ");
  v3 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v2, sub_411159);
  j_asmFun(v4, &v19 == &v19, v3, a1);
  v5 = sub_411802(std::cout, "Prime field p=127,base point G(11,4),publicK(120,41) please find private key k");
  v6 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v5, sub_411159);
  j_asmFun(v7, &v19 == &v19, v6, a1);
  v8 = sub_411802(std::cout, "input your flag:");
  v9 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v8, sub_411159);
  j_asmFun(v10, &v19 == &v19, v9, a1);
  getInput(a1, std::cin, &keyStringFromInput); // 獲取輸入
  v29 = &v16;
  v22 = sub_411447(&v16, a1, &keyStringFromInput);
  keyString = (struc_String *)calcuKey(a1, (int)&sting);// 根據輸入生成key
  keyString_1 = keyString;
  stringCopy(&keyStringFromInput, a1, keyString);
  releaseString(&sting, a1);
  goalBuf[0] = 0x5E; //目標結果字串
  goalBuf[1] = 0x26;
  goalBuf[2] = 0;
  goalBuf[3] = 0x13;
  goalBuf[4] = 94;
  goalBuf[5] = 38;
  goalBuf[6] = 86;
  goalBuf[7] = 94;
  goalBuf[8] = 94;
  goalBuf[9] = 38;
  goalBuf[10] = 45;
  goalBuf[11] = 121;
  goalBuf[12] = 94;
  goalBuf[13] = 38;
  goalBuf[14] = 4;
  goalBuf[15] = 5;
  goalBuf[16] = 94;
  goalBuf[17] = 38;
  goalBuf[18] = 90;
  goalBuf[19] = 116;
  goalBuf[20] = 94;
  goalBuf[21] = 38;
  goalBuf[22] = 49;
  goalBuf[23] = 73;
  goalBuf[24] = 94;
  goalBuf[25] = 38;
  goalBuf[26] = 8;
  goalBuf[27] = 41;
  goalBuf[28] = 94;
  goalBuf[29] = 38;
  goalBuf[30] = 94;
  goalBuf[31] = 55;
  goalBuf[32] = 94;
  goalBuf[33] = 38;
  goalBuf[34] = 53;
  goalBuf[35] = 2;
  goalBuf[36] = 94;
  goalBuf[37] = 38;
  goalBuf[38] = 12;
  goalBuf[39] = 92;
  goalBuf[40] = 94;
  goalBuf[41] = 38;
  goalBuf[42] = 98;
  goalBuf[43] = 50;
  goalBuf[44] = 94;
  goalBuf[45] = 38;
  goalBuf[46] = 57;
  goalBuf[47] = 41;
  goalBuf[48] = 94;
  goalBuf[49] = 38;
  goalBuf[50] = 16;
  goalBuf[51] = 51;
  goalBuf[52] = 94;
  goalBuf[53] = 38;
  goalBuf[54] = 102;
  goalBuf[55] = 18;
  v18 = j_getThis_0((int)&v27);
  v11 = sub_4112C1(&v26, (int)goalBuf, (int)&v24);
  createString(&goalString, a1, *v11, v11[1], (int)v18);
  if ( stringCmp((int)&keyStringFromInput, a1, (int)&goalString) )// key 驗證
    sub_41186B("try again", v19);
  else
    sub_41186B("Congratulations~~", v19);
  v12 = system("pause");
  j_asmFun(v13, &v19 == &v19, v12, a1);
  v25 = 0;
  releaseString(&goalString, a1);
  v35 = -1;
  releaseString(&keyStringFromInput, a1);
  v18 = v14;
  v17 = v25;
  sub_411677((int)&savedregs, (int)&dword_41BC54);
  return j_asmFun((unsigned int)&savedregs ^ v34, 1, v17, a1);
}


分析核心的key生成函式


生成函式的依據是橢圓曲線方程,用輸入字串的每個字元和字元的index進行倍點運算。

for ( i = 0; ; ++i )
  {
    len = getInpuKeyLen((int)sting, a1); // 獲取key長度
    if ( i >= len )
      break;
    index = i;
    oneKey = *(char *)getKeyByIndex((int)sting, a1, i);
    pointPair_1 = (struc_PointPair *)timesPiontByOneKey(a1, &pointPair, index, oneKey);
    copyPoint_1(&pointPair_2, a1, pointPair_1);
    j_addZuobiao2Buf_0(a1, (struc_String *)&resultStr, pointPair_2.p1_x);
    j_addZuobiao2Buf_0(a1, (struc_String *)&resultStr, pointPair_2.p1_y);
    j_addZuobiao2Buf_0(a1, (struc_String *)&resultStr, pointPair_2.p2_x);
    j_addZuobiao2Buf_0(a1, (struc_String *)&resultStr, pointPair_2.p2_y);
  }

運算過程如下:

int __usercall timesPiontByOneKey_0@<eax>(struc_Point *a1@<xmm0>, struc_PointPair *result, int index, int onekey)
{
  int y1AfterTimes; // edx
  int y2AfterTimes; // edx
  struc_Point p1; // [esp+100h] [ebp-20h]
  struc_Point p2; // [esp+110h] [ebp-10h]
  int v9; // [esp+11Ch] [ebp-4h]
  int savedregs; // [esp+120h] [ebp+0h]
 
  p2.x = j_timesPiont(a1, &G1, 17); // G1(11,4)->(0x5e,0x26)
  p2.y = y1AfterTimes;
  p1.x = index * j_timesPiont(a1, &G2, 17) % 127;// G2(14,91)->(0x56,0x5A)
  p1.y = onekey * y2AfterTimes % 127;
  copyPoint(result, (int)a1, (int)&p2, (int)&p1);
  sub_411677((int)&savedregs, (int)&dword_417788);
  return j_asmFun((unsigned int)&savedregs ^ v9, 1, (int)result, (int)a1);
}

程式中有連個G點,G1(11,4),G2(14,91);根據G1,G2分別生成P1和P2兩個點。

完成計算後分別將帶你的座標p.x,p.y儲存。

結果對比


生成目標字串和以計算出座標為內容的字串,進行比對。

createString(&goalString, a1, *v11, v11[1], (int)v18);
  if ( stringCmp((int)&keyStringFromInput, a1, (int)&goalString) )// key 驗證
    sub_41186B("try again", v19);
  else
    sub_41186B("Congratulations~~", v19);


逆向輸入

public class Q3CTF8 {
     
    static char [] result1 = {0x13,0x5E,0x79,0x05,0x74,0x49,0x29,0x37,0x02,0x5C,0x32,0x29,0x33,0x12};
    static char key;
     public static void main(String[] args)
     {
        for(int k = 0;k<result1.length;k++)
        {
            key = result1[k];
            for(int i =0;i<0xff;i++)
            {
                if(i*0x5A%127==key)
                {
                    System.out.printf("%c",i);
                    break;
                }
                 
            }
        }
         
     }
 
}

得到最終的key:Kanxue_2019_Q3。

相關文章