一:准備工作
1,三個二進制文件
bufbomb:一個有緩沖區溢出漏洞的程序。
makecookie:可以根據用戶不同的userid生成的唯一的cookie,userid不同,cookie不同,所要解決的方法就不同。
hex2raw:使編寫的緩沖區利用代碼的轉換為一個字符串的格式,只有經過轉換以后才可以輸入到getbuf中。
2,生成cookie的方法
3,bufbomb函數原型及介紹
getbuf函數類似於gets函數,它是讀入一段字符串,字符串以\n或者eof表示結束,並把存儲起來,但是getbuf提供的緩沖區只有32個字符大小,但是getbuf本身又對輸入的字符是否超過緩沖區大小進行安全檢查,從而帶來了緩沖區溢出漏洞。
使用方法:
參數:
-u,確保不同的userid用不同的cookie。
-n,為了level4,棧基址隨機化模式的時候使用。
-s,上傳到服務器進行打分。
4,提交方式
5,提示
二:實驗
Level 0:
實驗描述:
test函數調用getbuf函數,調用完以后還返回test函數,現在我們要做的是調用getbuf函數后,通過輸入我們的exploit,使得調用完以后不返回test函數了而是執行smoke函數。
test函數:
解決方法:
反匯編:
在反匯編結果中找到getbuf函數,smoke函數:
Getbuf函數:
畫出test函數調用getbuf函數的棧幀結構:
由反匯編結果可知,給輸入的字符串分配的空間是從%ebp-0x28開始的,換為10進制就是40個字節,而返回地址是在%ebp+0x4處,push %ebp本身又占了四個字節,所以結構為:
返回地址 4字節 |
Getbuf的,從%ebp到輸入字符串的空間為44個字節 |
Smoke函數:
由反匯編可得smoke函數的入口地址為0x08048e0a
綜上,我們需要做的就是把上面的44個字節隨意填滿(不要填換行),然后把原來的返回地址改為smoke函數的入口地址。0x0a是換行\n的ASCII值,所以不可以輸入,那么我們就輸入0x8048e0b來代替。
Level1:
實驗描述:
test函數調用getbuf函數,調用完getbuf以后不返回getbuf的調用者test而是去執行fizz函數。fizz函數要求傳入參數,參數必須是cookie。
fizz函數:
解決方法:
反匯編bufbomb找到fizz函數:
和level0類似,通過上一題已經知道了棧幀結構,所以我們需要做的還是把那44個字節填滿,然后再填寫fizz函數的入口地址(0x08048daf)用來覆蓋原來的返回地址。
關鍵:找到fizz函數的參數從棧中的什么地方傳入的,然后我們把我們的cookie寫進這個fizz會獲取參數的地方。實參只在主調函數中有效,形參只在被調函數中有效,我們要做的就是修改實參,它的位置就是在返回地址的上面4個單位。而返回地址已經被我們破壞,會默認它上面四個字節為返回地址,然后再向上4個字節來取參數。
執行leave后
所以答案如下:
Level2:
實驗描述:
執行完getbuf()后,不返回到test,而是去執行函數bang,但是區別是bang也要傳入參數,且參數是是一個全局變量。
bang函數:
解決方法:
反匯編:
看bang函數的反匯編代碼
可以看到,bang、函數的入口地址為0x08048d52.
接下來要做的就是改變全局變量global_value的值,使他的值為cookie
由匯編代碼,第四行 mov 0x804d10c,%eax 可以知道,global_value存放的位置是0x804d10c
由此寫下匯編代碼:
首先把我們的cookie寫到全局變量的地址中,然后在把bang的入口地址入棧,通過ret指令來執行bang函數
然后把.s文件變成字節碼:
利用gdb調試找到我們的exploit的地址了,用我們的地址來覆蓋返回地址,從而執行我們的代碼。
所以答案如下:
Level3:
修改getbuf()函數的返回值(正常狀態為0x1)為你的cookie值,然后讓函數正常返回到test.
解決方法:
函數調用棧,函數調用結束以后,棧被釋放,而返回結果會放在eax寄存器中。test不需知道調用的getbuf是怎么執行的,只需要到eax寄存器中去取返回值,所以可以在getbuf執行完以后,再把eax寄存器中的值動手腳修改為cookie。
有關棧的恢復:需要兩個部分,一個是ebp一個是eip的地址,一個是恢復pop出test的原ebp,所以在破壞之前,用gdb調試出來test的原ebp是多少記錄下來,恢復的時候在賦值給它。
用gdb來調試得到ebp的值:
原test的ebp是0x556839f0
eip的地址,就是返回地址,也就是test中在callgetbuf函數的下一條指令的地址:
call的下一條指令的地址是0x8048e50
所以.s代碼:
%eax的值改為cookie
%ebp的恢復,改成0x556839f0
把下一條指令地址0x8048e50壓入
返回
將.s文件變成字節碼:
所以答案是:
第一句 第三句 ret
填充0
%ebp恢復
自己的返回地址
Level4:
調用getbufn函數,
其緩沖區大小為512個字節, 且每次棧的位置都會變化
nop只是執行eip自加1不進行其他的操作。在無法猜測的時候,只需要找到最大的地址。
解決方法:
反匯編,查看getbufn反匯編結果。
buf的首地址為-0x208(%ebp)為十進制520個字節大小。
每次運行testn的ebp都不同,所以每次getbufn里面保存的test的ebp也是隨機的,但是棧頂的esp是不變的,我們就要找到每次隨機的ebp與esp之間的關系來恢復ebp。我們先通過調試來看一下getbuf里面保存的ebp的值的隨機范圍為多少。
ebp的值 減去0x208為buf的首地址
0x556839c0 0x556837b8
0x556839f0 0x556837e8
0x55683970 0x55683768
0x556839f0 0x556837e8
0x55683950 0x55683748
看testn的反匯編代碼:
call getbufn的下一條指令的地址為0x8048ce2
此外,還可以看到,mov %esp,%ebp 此時esp和ebp相等
push %ebx 此時ebp=esp+0x4
sub $0x24,%esp 這個時候執行完后,ebp=esp+0x28,這就是esp和ebp每次的變化關系,通過esp來恢復我們的每次的ebp
由此寫出以下匯編代碼:
將.s文件變成字節碼:
509個nop
15個字節碼,覆蓋篡改保存ebp
Buf首地址覆蓋返回地址,是可能的最大的首地址
三:總結
要想做明白這個實驗,就需要弄懂緩沖區溢出原理,以及堆棧的過程,函數調用的實現過程,函數傳參的底層實現等問題。並且,光理解原理也還遠遠不夠,還需要會應用。本次實驗的幾個level是逐步跟進的,一點一點的深入。Level0只需要理解原理,進行覆蓋地址,level1是修改參數,level2是修改全局變量,level3是恢復棧結構,level4是在level3第基礎上實現隨機化。在實驗的過程中還需要注意避免輸入換行對應的數字碼等等。
另外,在做本次實驗的過程中還遇到一個比較特別的問題,這個問題很隱蔽,難以發現,花費了很久才找到。在將自己寫的code4.s匯編的過程中,出現了機器碼生成錯誤的問題,mov指令對應的機器碼應該是b8,而在我的機器中,level4,卻生成了a1,至今還不知道為什么會出現這個問題。在與其他同學交流的過程中,發現其他同學也有過這種情況,是push指令對應的機器碼發生了錯誤。
截圖如下: