所謂緩沖區溢出,中文翻譯為Buffer overflow,意思是使用的緩沖區太小,容納不下。
那麽多東西,多余的東西都出來了。就好像水箱裝不下那麽多水,倒得太猛就會溢出來。
那麽,為什麽要在編程過程中使用緩沖區呢?簡單的答案是作為數據處理的中轉站。
2.2下C語言函數調用的機制。UNIX與緩沖區溢出的利用。
1)內存中進程的映像。
我們假設現在有壹個程序,它的函數調用順序如下。
主(...)-& gt;func_1(...)-& gt;函數_2(...)-& gt;函數_3(...)
即:主函數main調用函數func _ 1;函數func_1調用函數func _ 2;函數func_2調用函數func_3。
當壹個程序被操作系統調入內存時,其在內存中對應進程的圖像如下圖所示。
(存儲器高位地址)
+ - +
|...|...省略了壹些我們不需要關心的區域。
+ - +
| SHELL字符串|+ - argv字符串|+ -
+ - + /
| argv指針|/
+ - + /
| argc(命令行參數的數量)|/
+ - +
| main函數的堆棧框架|+-func _ 1函數的堆棧框架|+-+-func _ 3函數的堆棧框架| Stack (stack)
+......................................+ /
| | /
....../
| | /
+......................................+ /
|堆|/
+ - +
|未初始化(BSS)數據|未初始化數據(BSS)區域
+ - +
|初始化數據|初始化數據區
+ - +
文本|文本區域
+ - +
(低內存地址)
這裏需要說明的是:
I)隨著函數調用層數的增加,函數棧幀逐塊向內存低位地址延伸。隨著進程中函數調用次數的減少,也就是每壹次函數調用的返回,棧幀會被壹片壹片的拋棄,縮進內存的高位地址。每個函數的堆棧幀大小隨函數的性質而變化,這是由函數的局部變量的數量決定的。
Ii)進程對內存的動態應用發生在堆中。也就是說,隨著系統動態分配給進程的內存量的增加,堆可能會擴展到高地址或者低地址,這取決於不同CPU的實現。但總的來說是朝著內存的高地址增長的。
Iii)在BSS數據或堆棧的增長耗盡了系統分配給該進程的空閑存儲器的情況下,該進程將被具有更大存儲器模塊的操作系統阻塞並重新調度。(雖然與exploit無關,但知道就好)
Iv)函數的堆棧幀包含函數的參數(被調用函數的參數是放在調用函數的堆棧幀中還是放在被調用函數的堆棧幀中取決於不同系統的實現)、它的局部變量以及恢復調用函數的堆棧幀(即上壹個堆棧幀)所需的數據,包括調用函數的下壹個執行指令的地址。
v)非初始化數據(BSS)區用於存儲程序的靜態變量,這部分內存初始化為零。初始化數據區用於存儲可執行文件中的初始化數據。這兩個區域統稱為數據區。
Vi) Text(文本區)是壹個只讀區,任何向此區域寫入的企圖都將導致段中的非法錯誤。文本區域由運行可執行文件的多個進程共享。文本區域存儲程序的代碼。
2)函數的堆棧框架。
調用函數時建立的堆棧框架包含以下信息:
I)函數的返回地址。返回地址是存儲在調用函數的堆棧幀中,還是存儲在被調用函數的堆棧幀中,取決於不同系統的實現。
Ii)調用函數的堆棧幀信息,即堆棧的頂部和底部。
Iii)為函數的局部變量分配的空間
Iv)為被調用函數的參數分配的空間——取決於不同系統的實現。
3)利用緩沖區溢出。
從函數的棧幀結構可以看出:
因為壹個函數的局部變量的內存分配發生在堆棧框架中,如果我們在某個函數中定義了壹個緩沖區變量,那麽這個緩沖區變量所占用的內存空間就在調用該函數時建立的堆棧框架中。
因為對緩沖區的潛在操作(比如復制字符串)都是從內存中的低位地址到高位地址,而內存中存儲的函數調用的返回地址往往在緩沖區(高位地址)之上——這是由堆棧的特性決定的,它為函數返回地址的覆蓋提供了條件。當我們有機會用大於目標緩沖區大小的內容填充緩沖區時,我們可以重寫存儲在函數堆棧框架中的函數的返回地址,這樣程序的執行流程就會隨著我們的意圖而偏移。換句話說,這個過程在我們的控制之下。我們可以讓進程改變原來的執行流程,執行我們準備好的代碼。
這就是馮·諾曼的計算機架構的缺陷。
下面是緩沖區溢出利用率的示意圖:
I)函數對字符串緩沖區操作的方向壹般是從低地址到高地址。
例如:strcpy(s,“AAA ...”;
s s+1 s+2 s+3...
+ - + - + - + - + - +...+
(低內存地址)| a | a | | | a | | | | | | |(高內存地址)
+ - + - + - + - + - +...+
Ii)覆蓋函數返回地址
/| ...|(內存地址)
/ + - +
調用函數堆棧幀| 0x 41414141 |
\ + - +
\ | 0x 41414141 |調用函數的返回地址。
\+ - +
/| ......|
/ + - + s+8
/| 0x 41414141 |
/ + - + s+4
被調用函數堆棧幀| 0x 41414141 |
\ + - + s
\ | 0x 41414141 |
\ + - +
\| ......|
+....................+
|...|(低內存地址)
註意:字符A的十六進制ASCII碼值是0x41。
Iii)從上圖可以看出,如果我們用壹個進程可以訪問的地址而不是0x414141重寫調用函數的返回地址,而這個地址恰好是我們準備好的代碼的入口,那麽進程就會執行我們的代碼。否則,如果使用了進程無法訪問的段的地址,就會導致進程崩潰——段故障核心轉儲;如果這個地址有無效的機器指令數據,就會導致無效指令錯誤,以此類推。
4)當緩沖區在堆區或BBS區時。
I)如果緩沖區的內存空間是通過函數中的動態應用獲得的(比如用malloc()函數應用),那麽在函數的堆棧框架中只分配壹個指向堆中應用的內存空間的指針。在這種情況下,溢出發生在堆中,覆蓋相應函數的返回地址似乎幾乎是不可能的。使用這種情況的可能性要看具體情況,但也不是不可以。
Ii)如果緩沖區在函數中被定義為靜態,那麽緩沖區內存空間的位置在非初始化(BBS)區,類似於堆中的位置,所以可以使用。但是也有壹種特殊的情況,就是可以用它來覆蓋函數指針,讓進程在後面調用相應的函數,成為我們指定的代碼。
3.利用緩沖區溢出能得到什麽?
從上面可以看出,利用緩沖區溢出可以使我們重寫相關內存的內容和函數的返回地址,從而改變代碼的執行進程,讓進程執行我們已經準備好的代碼。
但是,該進程以我們當前登錄的用戶身份運行。如果我們能執行我們準備好的代碼呢?我們仍然無法突破系統對當前用戶的權限設置,也不能做超出權限的事情。
換句話說,如果我們想利用緩沖區溢出來獲得更高的權限,我們必須使用系統的壹些功能。
對於UNIX,有兩個特性可以使用。
壹)SUID和SGID方案
UNIX允許其他用戶以文件所有者的用戶ID或用戶組ID執行可執行文件,這是通過將可執行文件的文件屬性設置為SUID或SGID來實現的。也就是說,如果壹個可執行文件設置了SUID或SGID,當系統中的其他用戶執行該文件時,就相當於以所有者用戶或用戶組的身份執行該文件。如果可執行文件的所有者是root,並且該文件設置了SUID,那麽如果該可執行文件具有可用的緩沖區溢出漏洞,我們可以使用它來以root身份執行我們準備好的代碼。沒有什麽比讓它為我們生成壹個具有超級用戶root身份的SHELL更吸引人的了,不是嗎?