1. 程式人生 > >2018/11/19-hctf-LuckyStar

2018/11/19-hctf-LuckyStar

 

題目連結:https://pan.baidu.com/s/1wv1T4an04YaHTE5uo1Zvmw
提取碼:k228 

 

開啟程式後會播放一段音樂,音樂結束後會讓輸入key,輸入錯誤會顯示“Maybe next year”。

 

首先程式使用了Tls回撥函式進行反除錯部分。

 

 

IDA反編譯,發現不少反除錯,若檢測到除錯則輸出“LuckyStar!”,然退出程式。

 

 

 

 

有個二重do-while迴圈會檢測程序名並和一個字串陣列比較,字串陣列儲存的是六個常見的偵錯程式的名稱,若相等,則檢測到除錯,輸出“

LuckyStar”並退出程式。

 

 

 

對於這個反除錯我們選擇用winhex搜尋這些字串,然後修改掉即可。

 

程式在呼叫dword_407380這個函式時候會崩潰掉,我們用od把它給nop掉即可。

 

 

 

 

 

 

 

接著呼叫srand函式,0x61616161作為種子,然後以rand()%0x2018arr陣列中尋值,然後和0x401780地址的值開始亦或,進行自解密。

 

 

Tls函式結束後,就會執行自解密的程式碼部分。

 

當我們nop掉反除錯後,程式介面如圖,

 

 

並會放一段音樂,當音樂結束後會讓輸入。

 

對於反編譯自解密的部分,我們選擇當程式正在執行自解密部分的時候,使用ODattach功能附加到程式,然後右擊選擇“使用OD脫殼除錯程序”功能進行脫殼。

這裡我們並不是要真的脫殼,而是因為此時程式自解密部分已經解密了,我們那此時的程序記憶體dump下來,然後使用IDA即可找到自解密部分並能夠反編譯。

現在來分析播放完音樂才能輸入的功能,我們發現建立了一個新執行緒。

 

 

點進StartAddress看看

 

 

可以看到呼叫了PlaySoundA函式來播放音樂,當音樂放完會賦值dword_40737C1

然後只有當值為1時,才能跳出下面的while迴圈,然後執行輸入和驗證部分。

我們使用ODPlaySoundA函式給nop掉,然後就不必等待播放音樂了。

 

 

 

然後我們發現下面有第二段自解密,並且在獲取輸入後,輸入會作為引數傳進自解密部分。

 

 

 

然而當我們使用OD按下f9會發現程式崩潰,進行除錯發現是自第二段函式的自解密的資料是錯誤的,然後不用OD除錯則程式能正常執行,而第二段自解密的正確又只於rand()函式有關,自解密不對說明rand()是錯的,即srand的種子被改變了。也就是說——程式在某個地方有著隱藏的反除錯我們很難發現,如果檢測到除錯,srand()函式的種子就會被改變導致rand()生成值錯誤。

 

那麼既然它改變了srand()的種子,我們就在srand()函式內部的第一行處設斷點,當停下來時候找堆疊中的返回地址並檢視呼叫srand()的部分,當看種子在什麼時候被改變了。

 

 

這裡我們發現有兩個種子,我們進行除錯一下,發現使用od時使用的種子是0x10001165,而另一個種子是0x68637466,換成ascii碼就是“hctf”。我們直接修改指令使種子為0x68637466即可。

然後使用OD除錯執行即可自解密正確,然後獲取輸入進行校驗。

接著我們同樣使用OD附加上程式已經自解密完第二段函式的狀態,然後使用OD脫殼功能,再使用IDA反編譯第二個自解密函式。

 

我們來分析第二段反編譯的自解密函式。

首先前面的一堆虛擬碼可以根據一個byte4033C8陣列中的編碼表輕鬆看出來是base64加密,不過要注意的是編碼表被改了。

 

 

也就是先對傳入的輸入進行了base64加密,接著又對加密後的輸入依據rand()生成值進行了運算,思路比較簡單,不再過多分析。

 

 

 

接著跳出第二段自解密的函式後就是將運算後得到的資料與記憶體中的資料進行比較了。

 

 

 

可以找到第二段資料為

 

 

這樣的表示不太準確,我們用OD除錯可以得到比較的資料為

{0x49, 0xE6, 0x57, 0xBD, 0x3A, 0x47, 0x11, 0x4C, 0x95, 0xBC, 0xEE, 0x32, 0x72, 0xA0, 0xF0 ,0xDE,  

0xAC, 0xF2, 0x83, 0x56, 0x83, 0x49, 0x6E, 0xA9, 0xA6, 0xC5, 0x67, 0x3C, 0xCA, 0xC8, 0xCC, 0x5}  

 

現在我們已經理清了程式流程,先對輸入進行了編碼表變化了的base64加密,然後再進行運算,最後再和目標資料進行對比,然後我們只需要寫出指令碼即可。

 

#include<stdio.h>
#include<time.h>
int main(void)
{
    int i,j;
    int arr[] = {0x49, 0xE6, 0x57, 0xBD, 0x3A, 0x47, 0x11, 0x4C, 0x95, 0xBC, 0xEE, 0x32, 0x72, 0xA0, 0xF0 ,0xDE,  
0xAC, 0xF2, 0x83, 0x56, 0x83, 0x49, 0x6E, 0xA9, 0xA6, 0xC5, 0x67, 0x3C, 0xCA, 0xC8, 0xCC, 0x5};

    srand(0x68637466);
    for(i = 0; i < 384; i++)
    {
        rand();
    }

    for(i = 0; i < arr[i]; i++)
    {
        for(j = 6; j > -2; j -=2)
        {
            arr[i] ^= rand()%4<<j;
        }
        printf("%c",arr[i]);
    }
    printf("\n");
    return 0;
}

可以得到base64加密後的字串為“Agn0zNSXENvTAv9lmg5HDdrFtw8ZFq=”。

然後寫指令碼進行解密,注意編碼表問題。

//密文:Agn0zNSXENvTAv9lmg5HDdrFtw8ZFq=
//編碼表:bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/

#include <stdio.h> 
const char base[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"; 
char* base64_encode(const char* data, int data_len); 
char *base64_decode(const char* data, int data_len); 
static char find_pos(char ch); 
int main(int argc, char* argv[]) 
{
    char *enc = "Agn0zNSXENvTAv9lmg5HDdrFtw8ZFq=";
    int len = strlen(enc); 
    char *dec = base64_decode(enc, len); 
    printf("encoded : %s\n", enc); 
    printf("decoded : %s\n", dec); 

    return 0; 
} 

/* */ 
static char find_pos(char ch)   
{ 
    char *ptr = (char*)strrchr(base, ch);//the last position (the only) in base[] 
    return (ptr - base); 
} 
/* */ 

/* */ 
char *base64_decode(const char *data, int data_len) 
{ 
    int ret_len = (data_len / 4) * 3; 
    int equal_count = 0; 
    char *ret = NULL; 
    char *f = NULL; 
    int tmp = 0; 
    int temp = 0; 
    char need[3]; 
    int prepare = 0; 
    int i = 0; 
    if (*(data + data_len - 1) == '=') 
    { 
        equal_count += 1; 
    } 
    if (*(data + data_len - 2) == '=') 
    { 
        equal_count += 1; 
    } 
    if (*(data + data_len - 3) == '=') 
    {//seems impossible 
        equal_count += 1; 
    } 
    switch (equal_count) 
    { 
    case 0: 
        ret_len += 4;//3 + 1 [1 for NULL] 
        break; 
    case 1: 
        ret_len += 4;//Ceil((6*3)/8)+1 
        break; 
    case 2: 
        ret_len += 3;//Ceil((6*2)/8)+1 
        break; 
    case 3: 
        ret_len += 2;//Ceil((6*1)/8)+1 
        break; 
    } 
    ret = (char *)malloc(ret_len); 
    if (ret == NULL) 
    { 
        printf("No enough memory.\n"); 
        exit(0); 
    } 
    memset(ret, 0, ret_len); 
    f = ret; 
    while (tmp < (data_len - equal_count)) 
    { 
        temp = 0; 
        prepare = 0; 
        memset(need, 0, 4); 
        while (temp < 4) 
        { 
            if (tmp >= (data_len - equal_count)) 
            { 
                break; 
            } 
            prepare = (prepare << 6) | (find_pos(data[tmp])); 
            temp++; 
            tmp++; 
        } 
        prepare = prepare << ((4-temp) * 6); 
        for (i=0; i<3 ;i++ ) 
        { 
            if (i == temp) 
            { 
                break; 
            } 
            *f = (char)((prepare>>((2-i)*8)) & 0xFF); 
            f++; 
        } 
    } 
    *f = '\0'; 
    return ret; 
}

執行即可得到flag。