1. 程式人生 > >JCTF2014逆向題目小菜兩碟writeup

JCTF2014逆向題目小菜兩碟writeup

拿到檔案時  以為和第一題 小菜一碟一樣     又是一個.apk檔案

結果安卓模擬器安裝不了   發現不對勁   用winhex打開發現是一個PE檔案

於是加上.exe字尾    結果無法執行

Exeinfo PE載入發現不是一個PE檔案    猜測是做了手腳

用010Editor開啟     執行模板 EXE.bt

發現PE頭有問題      50 45 FF 00不對啊      正常應該是50 45 00 00才對

原因:struct IMAGE_NT_HEADERS NtHeader結構體中的DWORD Signature就是用來標識該檔案是否是PE檔案

該部分佔用4位元組,也就是“50 45 00 00”

該識別符號在Winnt.h中也有巨集定義,如下:

#define IMAGE_NT_SIGNATURE                  0x00004550  // PE00

那麼還有一個問題就是模板  沒有正確標識50 45 FF 00這四個位元組所在位置   (ps:有底色的部分是被標識到的)

這個是因為DOS頭最後一個欄位e_lfanew有問題

e_lfanew : 這個欄位儲存著PE頭[IMAGE_NT_HEADERS]的起始位置偏移

這個檔案中正確起始位置應該是E8h才對

好啦

修改好上述兩個位置之後  儲存成re200.exe就可以正常運行了

(ps:我的win10系統有毒  還得加上msvcp100d.dll  msvcr100d.dll這兩個dll才可以執行 2333....)

載入IDA進行分析     可以確定關鍵程式碼就在main_0()這個函式中

可以F5反彙編一下 

但裡邊變數有點亂   所以有些我重新命名了

根據棧中存放順序  調整名字  F5重新分析

v20 21 22 23 24 25分別對應以下四個變數

-0000005C var_5C          dd ?
-00000058 var_58          dd ?
-00000054 var_54          dd ?
-00000050 var_50          dd ?
-0000004C var_4C          dd ?
-00000048 var_48          dd ?

最後得到

__int64 main_0()
{
  int v0; // eax
  __int64 v1; // rax
  int v2; // eax
  int v3; // ST08_4
  int v4; // eax
  int v5; // eax
  int v6; // eax
  int v7; // eax
  int v8; // eax
  int v9; // eax
  int v11; // [esp-10h] [ebp-170h]
  int v12; // [esp-Ch] [ebp-16Ch]
  char *v13; // [esp-8h] [ebp-168h]
  int v14; // [esp-4h] [ebp-164h]
  int *v15; // [esp+Ch] [ebp-154h]
  int k; // [esp+D4h] [ebp-8Ch]
  int j; // [esp+E0h] [ebp-80h]
  int i; // [esp+ECh] [ebp-74h]
  char v19; // [esp+FBh] [ebp-65h]
  int v20; // [esp+104h] [ebp-5Ch]
  int v21; // [esp+108h] [ebp-58h]
  int v22; // [esp+10Ch] [ebp-54h]
  int v23; // [esp+110h] [ebp-50h]
  int v24; // [esp+114h] [ebp-4Ch]
  int v25; // [esp+118h] [ebp-48h]
  char v26; // [esp+14Ch] [ebp-14h]
  int v27; // [esp+14Dh] [ebp-13h]
  int v28; // [esp+151h] [ebp-Fh]
  char v29; // [esp+155h] [ebp-Bh]

  v0 = printf(std::cout, "歡迎來到數字遊戲 請輸入9個數字");
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v0, std::endl);
  v26 = 0;
  v27 = 0;
  v28 = 0;
  v29 = 0;
  v20 = 0;
  j_memset(&v21, 0, 0x3Cu);                     // memset方法為0x3C大小的記憶體做初始化(ps:全部用0填充)操作,返回值為指向s的指標
  for ( i = 0; i < 9; ++i )                     // 從v20 - v28定義九個變數
    std::basic_istream<char,std::char_traits<char>>::operator>>(std::cin, &v20 + i);
  if ( v22 * v21 * v20 / 11 != 106 )            // 逆過來就是 v22*v21*v20 / 11 == 106
    goto LABEL_31;                              // LABEL_31為失敗的地方   所以這樣的跳轉都不能讓它成立
  if ( (v21 ^ v20) != v22 - 4 )                 // 逆過來就是(v21 ^ v20) == v22 - 4
    goto LABEL_31;
  HIDWORD(v1) = (v22 + v21 + v20) % 100;        // (v22 + v21 + v20) % 100 == 34
  if ( HIDWORD(v1) != 34 )
    goto LABEL_31;                              // v23 ==  80
  if ( v23 == 80 )
  {
    for ( j = 0; j < 3; ++j )                   // 這個for迴圈可以忽略  與下面的if沒啥關係
    {
      HIDWORD(v1) = (j + 1) % 3;
      for ( *(&v26 + j) = *((_BYTE *)&v20 + 4 * HIDWORD(v1)) + *(&v20 + j % 3); ; *(&v26 + j) /= 2 )
      {
        while ( *(&v26 + j) < 33 )
        {
          HIDWORD(v1) = j;
          *(&v26 + j) *= 2;
        }
        if ( *(&v26 + j) <= 126 )
          break;
        v1 = *(&v26 + j);
      }
    }
    if ( v24 == 94 && v25 == 98 )               // v24 == 94 並且 v25 == 98
    {
      for ( k = 3; k < 9; ++k )                 // 這個for迴圈也沒什麼用  因為與下面的if沒啥關係
      {
        for ( *(&v26 + k) = *(&v26 + (k + 1) % 3) + *(&v26 + k % 3); ; *(&v26 + k) /= 2 )
        {
          while ( *(&v26 + k) < 33 )
            *(&v26 + k) *= 2;
          if ( *(&v26 + k) <= 126 )
            break;
        }
      }
      if ( !j_strcmp(&v26, "*&8P^bP^b") )
      {
        v2 = printf(std::cout, "success!");
        std::basic_ostream<char,std::char_traits<char>>::operator<<(v2, std::endl);
        v14 = std::endl;
        v13 = "abc}";
        v12 = v22;
        v11 = v21;
        v3 = v20;
        v15 = &v11;
        v4 = printf(std::cout, "jlflag{");
        v5 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v4, v3);
        v6 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v5, v11);
        v7 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v6, v12);
        v8 = printf(v7, v13);
        std::basic_ostream<char,std::char_traits<char>>::operator<<(v8, v14);
        sub_4112D0(std::cin, &v19);
        goto LABEL_32;
      }
LABEL_31:
      v9 = printf(std::cout, "please try again!");
      std::basic_ostream<char,std::char_traits<char>>::operator<<(v9, std::endl);
      goto LABEL_32;
    }
  }
LABEL_32:
  v14 = HIDWORD(v1);
  v13 = 0;
  return *(_QWORD *)&v13;
}

python指令碼跑下可以得到:

for v20 in range(100):
        for v21 in range(100):
                v22 = (v20 ^ v21) + 4
                if v20*v21*v22 // 11 == 106 and (v22 + v21 + v20) % 100 == 34:
                        print v20,v21,v22
'''
猜測最大數字不超過100 得到以下6組結果
6 13 15
6 15 13
13 6 15
13 15 6
15 6 13
15 13 6
'''

將他們與v23,v34,v25的值進行組合 再任意加上三個數字(比如1,2,3)

6 13 15 80 94 98 ? ? ?
6 15 13 80 94 98 ? ? ?
13 6 15 80 94 98 ? ? ?
13 15 6 80 94 98 ? ? ?
15 6 13 80 94 98 ? ? ?      一個一個輸入   得到這個是正確的
15 13 6 80 94 98 ? ? ?

歡迎來到數字遊戲 請輸入9個數字
15
6
13
80
94
98
1
2
3
success!
jlflag{15613abc}

下面是這次學到的彙編指令:

cdq指令它大多出現在除法運算之前。它實際的作用只是把EDX的所有位都設成EAX最高位的值

idiv是有符號數除法指令,完成兩個有符號數相除

memset是計算機中C/C++語言函式。將s所指向的某一塊記憶體中的前n個 位元組的內容全部設定為ch指定的ASCII值, 第一個值為指定的記憶體地址,塊的大小由第三個引數指定,這個函式通常為新申請的記憶體做初始化工作, 其返回值為指向s的指標。
  函式介紹:
  void *memset(void *s, int ch, size_t n);
  函式解釋:將s中前n個位元組 (typedef unsigned int size_t )用 ch 替換並返回 s 。
  memset:作用是在一段記憶體塊中填充某個給定的值,它是對較大的結構體或陣列進行清零操作的一種最快方法 

參考連結:

https://blog.csdn.net/m0_37812124/article/details/76396566

https://blog.csdn.net/z724133545/article/details/52044670