看雪WiFi萬能鑰匙CTF-第二題 lelfeiCM

    第一題解決以後,火燒眉毛地進入第二題,但是這猝不及防的難度提高,直接耗費了三天的時間來分析。這題是純算法的大數運算,很容易就找到算法關鍵點,可是分析算法須要耗費些時間。Talk is cheap, show you the process...python

  1. 老樣子,先放入IDA中靜態分析看看                                                                                                                           太過複雜,看看經過view string能不能找到關鍵點,果真看到了「wrong key」、「well done」字樣,而且在前面不遠處還看到fget()函數,後驗證得知fget()用於獲取輸入。                                          
  2. 既然以前有fget()獲取輸入,後面又有「well done」等結果提示,那麼compare就在中間,直接嘗試F5                                                                                                                                  
    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      signed int v3; // ecx@1
      int v4; // esi@3
      signed int v5; // edx@3
      char v6; // al@4
      int v7; // esi@11
      int v8; // esi@11
      signed int v9; // eax@13
      int v10; // edi@13
      int v11; // eax@13
      char *v12; // ecx@13
      int v13; // ST10_4@14
      int v14; // esi@14
      int v15; // eax@14
      int v16; // esi@14
      int v17; // eax@14
      char v19[260]; // [sp+8h] [bp-4130h]@1
      char v20; // [sp+10Ch] [bp-402Ch]@10
      char v21; // [sp+211Ch] [bp-201Ch]@11
      int v22; // [sp+4134h] [bp-4h]@10
    
      sub_401BE0(aPediyCtf2017);
      sub_401BE0(aCrackmeByLelfe);
      sub_401BE0(aPleaseInputKey);
      fgets(v19, 260, &stru_4090E0);
      v3 = strlen(v19) - 1;
      if ( v3 < 8 || v3 > 20 )
      {
        sub_401BE0(aKeyLenErrorD__);
        return 0;
      }
      v4 = 0;
      v5 = 0;
      v19[v3] = 0;
      if ( v3 > 0 )
      {
        do
        {
          v6 = v19[v5];
          if ( v6 <= 48 || v6 > 57 )
            ++v4;
          ++v5;
        }
        while ( v5 < v3 );
        if ( v4 )
        {
          sub_401BE0(aKeyFormatError);
          return 0;
        }
      }
      sub_4012C0(&v20, v5);
      v22 = 0;
      sub_4014E0(v19);
      nullsub_1(&v20);
      sub_401730(9);
      nullsub_1(&v20);
      while ( 1 )
      {
        sub_401350(v19);
        LOBYTE(v22) = 1;
        v7 = sub_401840(&v21);
        v8 = sub_401730(9) + v7;
        nullsub_1(&v20);
        if ( v8 || sub_4013A0(&v20) % 2 != 1 )
          goto LABEL_16;
        v9 = sub_4013A0(&v20);
        v10 = sub_4013B0(v9 >> 1);
        v11 = sub_4013B0(0);
        v12 = &v21;
        if ( v10 == v11 )
          break;
    LABEL_17:
        LOBYTE(v22) = 0;
        sub_401390(v12);
        if ( v8 )
        {
          sub_401BE0(aWrongKey___);
          goto LABEL_19;
        }
      }
      v13 = sub_4013A0(&v21) - 1;
      v14 = 1 - sub_4013A0(&v21);
      v15 = sub_4013A0(&v20);
      v16 = sub_4013E0(&v21, v15 + v14, 1, v13, 0);
      v17 = sub_4013A0(&v21);
      if ( sub_4013E0(&v21, 0, 1, v17 - 1, 1) + v16 )
      {
        v8 = 0;
    LABEL_16:
        v12 = &v21;
        goto LABEL_17;
      }
      sub_401BE0(aWellDone);
      LOBYTE(v22) = 0;
      sub_401390(&v21);
    LABEL_19:
      v22 = -1;
      sub_401390(&v20);
      return 0;
    }

    這是F5得出的反編譯結果,太過於複雜,經過分析將之優化獲得算法

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      signed int key_len; // ecx@1
      int v4; // esi@3
      signed int v5; // edx@3
      char v6; // al@4
      int v7; // eax@10
      int v8; // edx@10
      int v9; // edx@11
      int v10; // eax@11
      int v11; // edx@11
      int v12; // esi@11
      int v13; // eax@13
      signed int v14; // edi@13
      signed int v15; // eax@13
      char *v16; // ecx@13
      int v17; // ST10_4@14
      int v18; // esi@14
      int v19; // eax@14
      unsigned int v20; // esi@14
      int v21; // eax@14
      char key[260]; // [sp+8h] [bp-4130h]@1
      char big_num1; // [sp+10Ch] [bp-402Ch]@10
      char big_num2; // [sp+211Ch] [bp-201Ch]@11
      int v26; // [sp+4134h] [bp-4h]@10
    
      sub_401BE0(aPediyCtf2017);
      sub_401BE0(aCrackmeByLelfe);
      sub_401BE0(aPleaseInputKey);
      fgets(key, 260, &stru_4090E0);
      key_len = strlen(key) - 1;
      if ( key_len < 8 || key_len > 20 )
      {
        sub_401BE0(aKeyLenErrorD__);
        return 0;
      }
      v4 = 0;
      v5 = 0;
      key[key_len] = 0;
      if ( key_len > 0 )
      {
        do
        {
          v6 = key[v5];
          if ( v6 <= 48 || v6 > 57 )
            ++v4;
          ++v5;
        }
        while ( v5 < key_len );
        if ( v4 )
        {
          sub_401BE0(aKeyFormatError);
          return 0;
        }
      }
      random_init_big_num(&big_num1);
      v26 = 0;
      init_big_num(&big_num1, key);
      nullsub_1();
      multiple(v7, v8, &big_num1, 9);
      nullsub_1();
      while ( 1 )
      {
        init(&big_num2, key);
        LOBYTE(v26) = 1;
        v10 = multiple_big_num(&big_num2, v9, &big_num1, &big_num2);
        v12 = multiple(v10, v11, &big_num1, 9) + v10;
        nullsub_1();
        if ( v12 || get_len(&big_num1) % 2 != 1 )
          goto END_LABEL;
        v13 = get_len(&big_num1);
        v14 = get_ch(&big_num1, v13 >> 1);
        v15 = get_ch(&big_num2, 0);
        v16 = &big_num2;
        if ( v14 == v15 )
          break;
    LABEL_17:
        LOBYTE(v26) = 0;
        sub_401390(v16);
        if ( v12 )
        {
          sub_401BE0(aWrongKey___);
          goto LABEL_19;
        }
      }
      v17 = get_len(&big_num2) - 1;
      v18 = 1 - get_len(&big_num2);
      v19 = get_len(&big_num1);
      v20 = compare_big_num(&big_num1, &big_num2, v19 + v18, 1, v17, 0);
      v21 = get_len(&big_num2);
      if ( compare_big_num(&big_num1, &big_num2, 0, 1, v21 - 1, 1) + v20 )
      {
        v12 = 0;
    END_LABEL:
        v16 = &big_num2;
        goto LABEL_17;
      }
      sub_401BE0(aWellDone);
      LOBYTE(v26) = 0;
      sub_401390(&big_num2);
    LABEL_19:
      v26 = -1;
      sub_401390(&big_num1);
      return 0;
    }

    再次通過簡化獲得僞代碼dom

    BOOL Verify(const char *key)
    {
    	//在這次算法中,DWORD *big_num指向一個大緩衝區
    	//big_num[0]存儲0x4080C8(magic number),big_num[1]存儲大數位數
    	//big_num[1026]處開始存儲索引,即big_num[2+big_num[1026]]存儲第0位數,big_num[2+big_num[1026+i]]存儲第i位數
    	//big_num[2050]和big_num[2051]處存儲GetTickCount()獲得的隨機數種子
    	DWORD *big_num1=0x12BF58;
    	DWORD *big_num2=0x12BF68;
    	//tmp_garbage表明在調試時不用注意的多餘參數
    	int tmp_garbage[5];
    	signed int key_len = strlen(key) - 1;
    	if (key_len < 8 || key_len>20)
    		return FALSE;
    	key[key_len] = 0;
    	if (key_len > 0)
    	{
    		for (int i = 0; i < key_len; i++)
    		{
    			if (key[i] <= '0' && key[i]>'9')
    				return FALSE;
    		}
    	}
    	//經過GetTickCount()得到隨機種子,同時對緩衝區內容進行亂序排列
    	random_init_big_num(big_num1);
    	//將key中的數字存儲於big_num1中,輸入7521,反序存儲爲1257(一千二百五十七)
    	init_big_num(big_num1, key);
    	//big_num1*=9
    	multiple(tmp_garbage[0], tmp_garbage[1], big_num1, 9);
    	while (true)
    	{
    		random_init_big_num(big_num2);
    		init_big_num(big_num2, key);
    		//big_num1*=big_num2,此處result一定爲0
            //函數內部原理爲x*1586=x*10^3*1+x*10^2*5+x*10^1*8+x*10^0*6
    		int reslut = multiple_big_num(big_num2, tmp_garbage[2], big_num1, big_num2);
    		//big_num1*=9,同時由於最後一個參數是9,因此一定返回0
    		reslut += multiple(tmp_garbage[3], tmp_garbage[4], big_num1, 9);
    		//判斷big_num1的位數是否爲奇數
    		if (reslut || big_num1[1] % 2 != 1)
    			return FALSE;
    		//判斷big_num1的中間位數和輸入key的第一個數字(此時big_num2存儲key)
    		if (big_num1[big_num1[1026 + big_num1[1] / 2] + 2] == big_num2[big_num2[1026+0]+2])
    			break;
    	}
    	//PS:低位在前,高位在後;compare_big_num最後return時,會計算big_num1[2051]^big_num2[2051]來驗證調試器是否存在
    	//比較big_num1高big_num2[1]-1位,與big_num2中的高big_num2[1]-1位是否順序相同
    	//即假設big_num2有8位數字,比較big_num1的高7位與big_num2的高7位是否順序相同
    	int com_result=compare_big_num(big_num1, big_num2, big_num1[1] - big_num2[1] + 1, 1, big_num2[1]-1, 0);
    	//比較big_num1低big_num2[1]-1位,與big_num2中的低big_num2[1]-1位是否逆序相同
    	//即假設假設big_num2有8位數字,比較big_num1的低7位與big_num2的低7位是否逆序相同
    	com_result += compare_big_num(big_num1, big_num2, 0, 1, big_num2[1]-1, 1);
    	if (com_result)
    		return FALSE;
    
    	return TRUE;
    }

    具體過程須要進入函數內部觀察,才能推出函數做用,當函數過於模糊的時候可動態調試觀察先後結果來推導。函數

  3. 這次算法的大致思路是,輸入一個數X,程序計算Y=X*X*9*9,X的位數有n個,Y的中間數字須要等於X的第一位(最低位)數字,同時X的高n-1位和Y的高n-1位須要順序相同,X的低n-1位和Y的低n-1位須要逆序相同。經過Python簡化Verify獲得代碼:優化

    def verify(key):
        #檢測數字位數長度
        if len(key)<8 or len(key)>20:
            print("Wrong Number")
            return False
        #大數中不能有0
        for i in range(0, len(key)):
            if ord(key[i])<=48 or ord(key[i])>57:
                print("Wrong Number")
                return False
        #計算的到key*key*9*9
        alter_key = int(key)*int(key)*9*9;
        alter_key = str(alter_key)
        #逆序放置,低位在前,高位在後
        alter_key=alter_key[::-1]
        key = key[::-1]
        #判斷alter_key中間位數與key的最低位
        middle_idx = int(len(alter_key)/2)
        if alter_key[middle_idx] != key[0]:
            print("Wrong Number")
            return False
        #判斷alter_key的高len(key)-1位和key的高len(key)-1位是否順序相同
        x = len(alter_key)-len(key)+1
        y = 1
        for i in range(0,len(key)-1):
            if alter_key[x]!=key[y]:
                print("Wrong Number")
                return False
            x+=1
            y+=1
        #判斷alter_key的低len(key)-1位和key的低len(key)-1位是否逆序相同
        for i in range(0,len(key)-1):
            if alter_key[i] != key[len(key)-1-i]:
                print("Wrong Number")
                return False
        return True
    
    i = 11111111
    while verify(str(i)) != True:
        i += 1
    print(i)

                                                                                                                                                   

  4. 獲得結果12345679,傳說中的缺八數,但是程序是逆序存儲的,先輸入的數字表示低位,因此答案是97654321。驗證結果,正確無誤                                                                                                                         調試

相關文章
相關標籤/搜索