CS:APP bomblab 記錄

任務就是把bomb這個可執行文件裏的炸彈拆掉,他有6組數據,你需要輸入6次來拆除這個炸彈。

 

簡化流程

你可以新建一個文件叫psol.txt,然後通過下面的執行命令

./bomb psol.txt

即可把psol.txt裏的數字自動輸入到bomb裏。理論上直接重定向輸入流<也是可以的吧。

 

Hint

1.學會用gdb。

2.objdump -t可以打印可執行文件的符號表。

3.objdump -d反編譯可執行文件,列出所有指令。

4.strings 顯示可打印的字符串。

 

Phase_1 

首先我們來反編譯一下看看彙編。

我們可以觀察可以到main部分有相似的函數調用。

 

這裏看到有裏那個個4個call,phase_1,phase_defused,phase_2,phase_defused。所以我們可以猜到每顆炸彈都是傳參到一個函數去處理的,我們來看我們的第一顆炸彈處理函數phase_1。

前面四句基本上是函數調用都會有的指令了,就是分配棧幀,第5句我沒理解有什麼用。。。然後再後面三句就是調用string_not_equal函數,光看名字我都知道這個就是判字符串是否不等了。。。後面是判定返回值,zf=0則調用explode_bomb,否則就返回了。。。看到這裏基本上就可以確定這題了,只要查看0x80497c0的內存是什麼我們就能知道答案了。。下面來gdb調試一下。

首先我們打斷點,然後單步執行到調用strings_not_equal前,然後把調用strings_not_equal的兩個參數打出來,一個是xxx(就是我剛纔隨便輸的一串),另一個是Public speaking is very easy.(這個就是答案).爲什麼我要把兩個都打出來呢?因爲我調用這個phase_1傳過來的參數不是我原始的輸入參數,可能main裏面做了手腳,結果打印出來是沒有的,那麼這道題就解決了。

 

phase_2

同樣先看看phase_2函數的指令。

前面幾句不用分析了吧。後面我們先看第一句關鍵指令call read_six_numbers,從這個名字我們可以看出他調用了一個函數,這個函數可以讀出6個數字,再看前面兩個push,這個函數兩個參數一個edx,一個是eax,eax是前面一條指令賦值過來的,是ebp-0x18,這個是phase_2分配的一個局部變量,edx是ebp+8,這個參數是phase_2的參數,也就是我們的輸入。我們打印一下這兩個數據。

然後我們再看看read_six_number。

可以看到調用了一個sscanf,然後這個sscanf壓了8個參數,sscanf的第一個參數是ebp+8,也就是傳過來的第一個參數也是我們的輸入,第二個參數在0x8049b1b,我們可以猜到這個參數是"%d %d %d %d %d %d",等會會打印一下,然後剩下6個參數都是基於edx算的地址,edx是ebp+c,這是傳過來的第二個參數,那麼基本已經明確了,傳過來一個是我們的輸入一個是轉換成6個整數後的數組,這裏我們可以看到他還判定了一下sscanf的返回值是不是<5,也就是不滿足賦值6個數就會bomb,我們可以打印一下0x8049b1b。

好,我們再返回看phase_2。我們可以看到他對第一個數字作了判斷是否=1,不等於就bomb,那麼我們可以確定輸入的第一個數字是1,後面的指令解說太複雜了,經過我化簡成c++,代碼大概如下。

for(int ebx = 1; ebx<=5; ++ebx){
	int eax = ebx + 1;
	eax = eax * arr[ebx - 1];
	if (arr[ebx] != eax)
		explode_bomb();
}

有了這個就很好推輸入了,答案就是1 2 6 24 120 720。

 

phase_3

同樣看一下彙編代碼。這個稍微長點,我們分步解析。

首先是一個sscanf,函數,看參數數量可以知道是將輸入串轉爲3個參數,我們打印一下在地址0x80497de的格式串。

可以看到是輸入是三個參數。

後面這一串是先判定sscanf返回值是否>2,不>2證明賦值不夠3個那麼就bomb,再後面判定第一個參數要小於7,再後面就到了無條件jmp語句了。。。這裏應該是個switch語句,但是我沒有時間去把switch語句全解析出來了,過於複雜,直接第一個參數往1套,然後最終先到達的第一個分支點是這裏。

ebp-4這個位置是我們的第三個參數,如果不等於0xd6就會bomb,那麼我們的第三個參數就出來了,然後又跳到下一個分支點。

這裏如果第二個參數不等於bl寄存器的值那麼就bomb,我們來看看bl寄存器的值。

那麼答案就全部出來了,就是1 b 214。

 

phase_4

同樣,來看看phase_4函數。

慣例的sscanf,這次只有三個參數,盲猜0x8049808是"%d",查看一下。

確定了,輸入就一個整數,然後後面熟悉的判定sscanf返回值,不說了。

再後面的其實也很好理解說實話,畢竟前面都搞了三個了,這個有前三個的基礎直接看都能看懂了,我來給大家轉換成c語言吧,大致就是這樣。

if(x <= 0)
    explode_bomb();
int eax = func4(x);
if(eax  != 0x37)
    explode_bomb();

int func4(int x){
    if(x <= 1)
        return 1;
    int esi = func4(x - 1);
    int eax = func4(x - 2);
    eax += esi;
    return eax;
}

看不懂的小夥伴根據我的c代碼來對照一下吧,語句也不復雜的,轉換成這樣之後就很好辦了,很顯然func4是個用遞歸實現的斐波那契數列,然後需要輸入的是斐波那契在第幾層==0x37,那麼答案就出來了,就是9。

 

phase_5

同樣看一下彙編代碼。

做了前面這麼多,這一段不用我多說了吧,就是判一下輸入是不是6位的字符串,不是就bomb。

再後面我們看到有幾個常量,首先我們需要知道他是什麼,下面打印一下。

第一個:

第二個:

接下來就好辦了,又到了我最會的轉換成c語言環節,上面這一大段用c語言表示就是這樣。

char *input;//我們的輸入
char *ebx = input;
int eax = 6;
char ecx[8];
char *esi = "isrveawhobpnutfg\260\001"
for(int edx = 0; edx <= 5; ++edx){
    char al = ebx[edx]
    al &= 0xf;
    eax = al;
    al = esi[eax]
    ecx[edx] = al;
}
ecx[6] = '\0';
if(strings_not_equal(ecx, "giants"))
    explode_bomb();

然後根據這一段代碼反推這個字符串中的每個字符的末尾四個bit是15 0 5 11 13 1。

然後我通過將這些數字+32後對照ascill碼錶得到其中一組答案:/ %+-!。

 

phase_6

這題有毒啊,跟前面完全不是一個難度,我下班抽空搞搞了幾天才弄出來。直接給翻譯後的代碼和註釋,還有我畫的內存圖,然後對着看吧,不想過多解釋了。。。。。

首先是內存圖。。。。畫得有點醜,大家能看懂就行,我把裏面定義的變量自己畫了出來用了別名來代替,不然完全沒法看懂。

然後是轉換後的c代碼。read_six_number那裏我就不寫了吧。


struct Node{
    int x, y;
    Node *next;
};
int a[6];
Node *b = 0x804b26c;
int c;
int edi;
for(edi = 0; edi <= 5; ++edi){
    int eax = a[edi];
    eax--
    if(eax > 5)
        explode_bome();
    for(int ebx = edi + 1; ebx <= 5; ++ebx){
        int *esi = a;
        if (esi[edi] == esi[ebx])
            explode_bome();
    }
}
/*
 * 上面是判斷輸入的數兩兩之間不相等。
 */
edi = 0;
Node *d[6];
int *ecx = a;
Node **e = d;
for(edi = 0; edi <= 5; ++edi)
    Node *esi = b;
    int ebx = 1;
    for(int ebx = 1; ebx < ecx[edi]; ++ebx)
        esi = esi->next;
    Node **edx = e;
    edx[edi] = esi;
}
/*
 * 簡單說明一下,b是鏈表的頭節點(包含數據的,並非無數據的頭)
 * 舉例當edi爲1時,a[edi]=2時,會把鏈表的第二個節點放入d[1]中
 */
Node *esi = d[0]
b = esi;
Node **edx = d;
for(edi = 1; edi <= 5; ++edi){
    Node *eax = edx[edi]
    esi->next = eax;
    esi = eax;
}
esi->next = NULL;
//這個好理解了吧,就是把數組d給串起來,構造鏈表
esi = b;
for(int edi = 0; edi <= 4; ++edi){
    Node *edx = esi->next;
    if(esi->x < edx->x)
        explode_bomb();
    esi = esi->next;
}
//這個也很好理解,新的鏈表必須前面比後面大,也就是降序

這樣一看就清晰了吧,結果就是把鏈表重新降序排序一遍,我們輸入的是他原來的index。最後再把鏈表打出來看看。

顯然,答案就是4 2 6 3 1 5。最後全部通過。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章