week2刷題

目錄

  • week2刷題
  • 1. [Zer0pts2020]easy strcmp
  • 2. [buuctf] findKey
  • 方法一:
  • 方法二:
  • 3. [FlareOn5]Ultimate Minesweeper

1. [Zer0pts2020]easy strcmp

下載後直接IDA打開題目,結構非常的簡單,直接看main函數

LeetCode刷題筆記_第二週_反編譯

只有一個赤裸裸的strcmp,進去看看有什麼操作:

LeetCode刷題筆記_第二週_賦值_02

此時這個strcmp大概率就是被hook了。

在ELF當中,init會比main先執行,可以看看init裏面的函數做了什麼:

LeetCode刷題筆記_第二週_賦值_03

也就是這三個函數:

LeetCode刷題筆記_第二週_反編譯_04

逐個查看,發現在sub_795當中hook的strcmp:

LeetCode刷題筆記_第二週_字符串_05

而hook函數就是sub_6EA,接下來看看這個函數怎麼回事:

__int64 __fastcall sub_6EA(__int64 a1, __int64 a2)
{
  int i; // [rsp+18h] [rbp-8h]
  int v4; // [rsp+18h] [rbp-8h]
  int j; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; *(_BYTE *)(i + a1); ++i )
    ;
  v4 = (i >> 3) + 1;
  for ( j = 0; j < v4; ++j )
    *(_QWORD *)(8 * j + a1) -= qword_201060[j];
  return qword_201090(a1, a2);
}

這裏就是先進行了 - qword_201060的加密,然後調用原來的strcmp,這裏是將輸入的字符串,每8個字節減去qword_201060當中的一個數,最後得到最開始的strcmp的字符串,知道這個就能直接寫exp了,這裏使用C/C++寫比較好,python對這種整型類型處理不太方便,容易出錯:

#include <stdio.h>
int main()
{
    char enc[] = "zer0pts{********CENSORED********}";
    unsigned long long key[] = {0, 0x410A4335494A0942, 0x0B0EF2F50BE619F0, 0x4F0A3A064A35282B};
    for(int i = 0; i < 4;i++)
    {
        ((unsigned long long*)enc)[i]  = ((unsigned long long*)enc)[i] + key[i];
    }
    for(int i = 0;i < 32;i++)
    {
        printf("%c",enc[i]);
    }
}

最後解出flag為:zer0pts{l3ts_m4k3_4_DETOUR_t0d4y}

2. [buuctf] findKey

這題是我認為比較有意思的一道題,首先下載下來的題目運行一下,是一個win32寫的題目:

LeetCode刷題筆記_第二週_字符串_06


LeetCode刷題筆記_第二週_字符串_07

一共有兩個對話框,然後有一個是在help當中的About選項當中。這個時候還不知道要怎麼解這個題。那麼直接IDA打開題目:

LeetCode刷題筆記_第二週_反編譯_08

很快就找到了主對話框的回調函數,不過加了花指令,需要整理一下才能夠反編譯,反編譯之後是這樣的:

LeetCode刷題筆記_第二週_字符串_09

LeetCode刷題筆記_第二週_字符串_10

方法一:

分為了上下兩個部分,先來看上部分的if,這裏就是如果窗口接收到0x111,也就是WM_COMMAND的消息就進入if。517和520分別是0x2050x208,對應的消息是鼠標左鍵抬起鼠標右鍵抬起,如果接收到0x205就進入String1的判斷流程,具體判斷流程如下:

  1. 判斷String1是否為空
  2. 不為空則將String1丟到sub_40101E函數當中運算,經過分析為md5
  3. 接着將字符串
"0kk`d1a`55k222k2a776jbfgd`06cjjb"

SS進行sub_401005的運算,這裏其實是xor

  1. String1加密後的值與上面字符串xor後的字符串進行比較,如果不正確則彈出對話框
  2. 如果正確則使用sub_401005將求出的flag輸出出來。(這裏的v18String1原來的值,而v10為密文)

經過上面的推斷已經可以寫出exp解出腳本了,

LeetCode刷題筆記_第二週_反編譯_11


LeetCode刷題筆記_第二週_字符串_12

這裏解出String1的值就是123321。然後再使用python解就能解出flag

enc = bytes.fromhex("57 5E 52 54 49 5F 01 6D 69 46 02 6E 5F 02 6C 57 5B 54 4C")
key = b"123321"
for i in range(len(enc)):
    print(chr(enc[i]^key[i % 6]),end="")
  
# flag{n0_Zu0_n0_die}

到這一步就已經解出來了。

方法二:

有趣的部分並不在上面過程,對於整個程序的邏輯也沒有理清楚。String1還不知道是從哪裏來的,再來繼續往下分析。看到下面的部分

LeetCode刷題筆記_第二週_字符串_10

這裏當消息當中的wParam為104的時候會創建一個Dlg,那麼很容易就能猜到是這個About對話框了,而這個DialogFunc就是這個About對話框的回調函數,跟進分析一下:

LeetCode刷題筆記_第二週_反編譯_14

整個回調函數邏輯很簡單,就是判斷接收到的消息是否是對應的消息,如果是對應的消息就進行相應的操作,而這裏的0x202,0x205,0x208,分別就是左鍵抬起,右鍵抬起,中鍵抬起。然後給String1賦值,原來String1是從這裏來到。而這個程序算是用鼠標事件激活的一個CrackMe,通過使用鼠標事件來將String1拼接成123321,然後當作輸入解出flag,所以我們可以通過這個方法來解出flag,腳本如下:

void decrypt()
{
    HWND hWnd = FindWindow(NULL, TEXT("find flag"));
    if (!hWnd)
    {
        printf("[+] : 沒有找到指定窗口\r\n");
        return;
    }
    printf("[+] : 已經找到find flag窗口\r\n");
    printf("[+] : find flag窗口句柄 = %x\r\n", hWnd);
    ::PostMessageA(hWnd, 0x111, 0x68, 0);   // 打開about
    printf("[+] : 打開About成功\r\n");
    Sleep(5000);

    HWND hAbout = FindWindow(TEXT("#32770"), TEXT("About"));
    if (!hAbout)
    {
        printf("[+] : 沒有找到指定窗口\r\n");
        return;
    }
    printf("[+] : 已經找到About窗口\r\n");
    printf("[+] : About窗口句柄 = %x\r\n", hAbout);
    
    printf("[+] : 即將發送消息.....\r\n");
    UINT msg[] = { 0x202,0x208,0x205,0x205, 0x208 ,0x202 };
    for (int i = 0; i < 6; i++)
    {

        ::SendMessageA(hAbout, msg[i], 0, 0);
        //Sleep(500);
        printf("[+] : 已向子窗口發送消息 %x\r\n", msg[i]);
    }
    ::SendMessageA(hAbout, 0x111, 1, 0);
    printf("[+] : About窗口處理完成\r\n");

    ::SendMessageA(hWnd, 0x205, 50, 50);
    printf("[+] : 開始校驗...");
}

這個腳本要注意的是,最後向主窗口發送0x205的時候,lParam需要包含鼠標的X和Y座標,如果直接填0,則設置成(0,0)。在窗口客户區的左上角,需要移動一下位置才能收到這個消息。

通過腳本向指定窗口發送消息,這樣就能夠讓這個題目直接將flag輸出出來了:

LeetCode刷題筆記_第二週_賦值_15

3. [FlareOn5]Ultimate Minesweeper

這是一道C#的程序,打開之後就是一個掃雷:

LeetCode刷題筆記_第二週_字符串_16

隨機點擊一個格子,如果踩到雷就會失敗:

LeetCode刷題筆記_第二週_反編譯_17

這個題目可以網上找些掛一把梭了,但是這裏不準備這樣做。

那麼直接使用dnSpy來反編譯這個程序,進來就看到一個SuccessPopup的方法了,直接跟過去

LeetCode刷題筆記_第二週_反編譯_18

然後通過分析找出調用SuccessPopUp這個函數的地方:

LeetCode刷題筆記_第二週_賦值_19

這裏可以看到判斷是判斷了當MineField當中的一個值為0的時候就調用這個成功的函數,而上面的if看名稱應該就是踩到炸彈的判斷了,進去看看這個雷是專門判斷的:

LeetCode刷題筆記_第二週_反編譯_20

可以看到是通過遍歷MinesPresentMinesVisible來判斷的,而這兩個東西猜測就是地圖,一個是雷,另一個應該是遮住雷的方塊。只有當這兩個都為true的時候才算踩到雷,那麼換個角度其實就是當true是雷和遮擋,分析一個這兩個成員的set的方法,看看在哪裏初始化:

LeetCode刷題筆記_第二週_字符串_21

可以看到是在這個MineField當中被初始化的。那麼通過動態調試就可以直接看出這個內存當中的雷在什麼位置了。那麼就直接啓動調試:

LeetCode刷題筆記_第二週_字符串_22

不過經過調試之後發現,這個雷的map全是false,那就是説明這裏只是申請了空間,並沒有埋雷,那麼就繼續步過看看什麼時候變成的true:

LeetCode刷題筆記_第二週_字符串_23

沒走兩步經過這裏之後就看到了MinesPresent當中已經被賦值成true了,那麼賦值應該就是在這個AllocateMemory當中賦值的,進入裏面看看情況:

LeetCode刷題筆記_第二週_字符串_24

這個就是給雷區賦值的了,注意這裏是將展現給玩家的map,行列互換之後再賦值到MinesPresent當中的,所以MinesPresent找出來的座標映射到真正的地圖上時要交換座標,其次還需要行列+1才是真正的map的位置,直接斷點打在這個GarbageCollect,然後就能找到座標了:

LeetCode刷題筆記_第二週_字符串_25

LeetCode刷題筆記_第二週_字符串_26

LeetCode刷題筆記_第二週_賦值_27

然後就能通過掃雷解出flag了:

LeetCode刷題筆記_第二週_賦值_28

選中就是flag了。