緩沖區溢出以及緩沖區溢出攻擊


緩沖區溢出是指當計算機程序向緩沖區內填充的數據位數超過了緩沖區本身的容量。溢出的數據覆蓋在合法數據上。理想情況是,程序檢查數據長度並且不允許輸入超過緩沖區長度的字符串。但是絕大多數程序都會假設數據長度總是與所分配的存儲空間相匹配,這就為緩沖區溢出埋下隱患。

操作系統所使用的緩沖區又被稱為堆棧,在各個操作進程之間,指令被臨時存儲在堆棧當中,堆棧也會出現緩沖區溢出。 當一個超長的數據進入到緩沖區時,超出部分就會被寫入其他緩沖區,其他緩沖區存放的可能是數據、下一條指令的指針,或者是其他程序的輸出內容,這些內容都被覆蓋或者破壞掉。可見一小部分數據或者一套指令的溢出就可能導致一個程序或者操作系統崩潰。

    #include <stdio.h>  
    #include <string.h>  
    #include <iostream>  
      
    using namespace std;  
      
    int main(int argc, char *argv[])  
    {  
        char buf[10];  
        strcpy(buf, argv[1]);  
        cout<<buf;  
        return 0;  
    }  
連續輸入20個字符就產生了溢出。

C語言常用的strcpy、sprintf、strcat 等函數都非常容易導致緩沖區溢出問題。

程序運行時,其內存里面一般都包含這些部分:

(1)程序參數和程序環境;

(2)程序堆棧(堆棧則比較特殊,主要是在調用函數時來保存現場,以便函數返回之后能繼續運行),它通常在程序執行時增長,一般情況下,它向下朝堆增長

(3)堆,它也在程序執行時增長,相反,它向上朝堆棧增長;

(4)BSS 段,它包含未初始化的全局可用的數據(例如,全局變量);

(5)數據段,它包含初始化的全局可用的數據(通常是全局變量);

(6)文本段,它包含只讀程序代碼。 

BSS、數據和文本段組成靜態內存:在程序運行之前這些段的大小已經固定。程序運行時雖然可以更改個別變量,但不能將數據分配到這些段中。

以下面的程序為例:

 

    #include <stdio.h>  
      
    char buf[3] = "abc";  
    int i;  
      
    int main()  
    {  
        i = 1;  
        return 0;  
    }  
其中,i屬於BBS段,而buf屬於數據段。兩者都屬於靜態內存,因為他們在程序中雖然可以改變值,但是其分配的內存大小是固定的,如buf的數據大於三個字符,將會覆蓋其他數據。 

與靜態內存形成對比,堆和堆棧是動態的,可以在程序運行的時候改變大小。堆的程序員接口因語言而異。在C語言中,堆是經由malloc()和其它相關函數來訪問的,而C++中的new運算符則是堆的程序員接口。堆棧則比較特殊,主要是在調用函數時來保存現場,以便函數返回之后能繼續運行。

 


 

緩沖區溢出攻擊


緩沖區溢出攻擊簡單介紹:

緩沖區溢出的一個致命的使用就是讓程序執行它本來不願意執行的函數。這是一種常見的通過計算機網絡攻擊系統安全的方法。通常,輸入給程序一個字符串,這個字符串包含一些可執行代碼的字節編碼,成為攻擊代碼。另外,還有一些字節會用一個指向攻擊代碼的指針覆蓋返回地址。那么,執行ret指令的效果就是跳轉到攻擊代碼。My god, 黑客順利侵入。
 
一種攻擊形式,攻擊代碼會使用系統調用啟動一個外殼程序,給攻擊者提供一組操作系統函數。
另一種攻擊形式,攻擊代碼會執行一些未授權的任務,修復對棧的破壞,然后第二次執行ret指令,(表面上正常返回給調用者)。

那么如何防護/對抗緩沖區溢出攻擊?
(1) 棧隨機化(主要受linux系統版本限制,老版本不支持棧隨機化):使得棧的位置在程序每次運行時都有變化。為了在系統插入攻擊代碼,攻擊者不但要插入代碼, 還需要插入指向這段代碼的指針(指向攻擊代碼的首地址/棧地址),這個指針也是攻擊字符串的一部分。產生這個指針需要知道這個字符串放置的棧地址。老的系統版本,如果在相同的系統運行相同的程序,棧的位置是相當固定的。所以黑客可以在一台機器上研究透系統上的棧是如何分配地址的,就可以入侵其它主機。
實現的方式:程序開始時,在棧上分配一段0~n字節之間隨機大小的空間。分配的范圍n必須足夠大,才能獲得足夠多樣的棧地址變化,但是又要足夠小,不至於浪費程序太多的空間。tradeoff
 
(2) 棧破壞檢測(主要受GCC版本的限制,老的GCC版本不支持棧破壞檢測):檢測到何時棧被破壞。從strcpy等函數我們可以看到,破壞通常發生在當超越局部緩沖區的邊界時。在C語言中, 沒有可靠的方法來防止對數組的越界寫。但是,我們能夠在發生了越界寫的時候,並且,在其還沒有造成任何有害結果之前,嘗試檢測到它,並且把程序終止。
實現的方式金絲雀,加入一種棧保護機制 在棧幀中,緊接着局部緩沖區的位置放置一個哨兵(金絲雀),哨兵值是隨機產生的,攻擊者沒有簡單的方法能夠知道它是什么。在恢復寄存器狀態和函數返回之 前,程序檢查這個金絲雀的值是否發生改變,如果發生改變立即終止程序。《深入理解操作系統》P182頁,有一個特別好的例子。
 
(3) 限制可執行代碼區域(主要受硬件版本的限制,需要硬件支持):消除攻擊者向系統插入可執行代碼的能力,一種方法是:限制那些能夠存放可執行代碼的存儲器區域。在典型的系統中, 只有保存編譯器產生的代碼的那部分存儲器才需要是可執行的,其它部分可以被限制為只允許讀和寫。
一般的系統允許三種訪問的形式: 讀(從存儲器讀數據)、寫(存儲數據到存儲器)和執行(將存儲器的內容看作是機器級代碼)。以前, x86體系結構將讀和執行訪問控制合並為1位的標志,這樣任何被標記為可讀的頁都是可執行的。棧又要求必須是既可以讀又可以寫的,所以x86體系結構棧上的字節都是可執行的。也有一些體制,能夠限制一些頁是可讀但是不可執行,但是這些體制一般都會帶來嚴重的性能損失。
實現的方式:AMD為它的64位存儲器的內容保護引入了“NX”(No-eXecute,不執行)位,將讀和執行訪問模式分開,intel也跟進了。從這開始,棧可以被標記為可讀、可寫,但是不可執行。檢查頁是否可執行由硬件來完成,效率上沒有損失。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM