天天看點

小議緩沖區溢出

​        最近一個朋友學習資訊安全方面的知識,然後發來一題和我一起讨論,雖然覺得簡單,但是實際還是有點意思的,就拿出來一起看看。題目如下:​

小議緩沖區溢出

​        從圖中可以看到一段C語言的代碼,還能看到3個問題。這裡我把代碼提出來,代碼如下:​

#include <stdio.h>


int main(int argc, char* argv[])
{
    int apple;


    char buf[9];


    gets(buf);


    if (apple == 0x64636261) 
    {
        printf("hello world!");
    }


    return 0;
}      

​我把問題也寫出來,問題有三個:​

​(1)分析是哪種溢出類型​

​(2)給出題目的變量 apple 的位址,例如 0x0012ff44,給出 buf 各字元的位址​

​(3)a、b、c、d 的 ASCII 碼值分别為0x61、0x62、0x63 和 0x64 ,給出 buf 輸入方式,使得程式可以輸出 hello world​

​什麼是緩沖區​

​        簡單說,緩沖區就是一塊存放資料的記憶體區域。根據存放資料的記憶體的配置設定方式,可以把記憶體分為棧記憶體和​堆記憶體​。​

​        棧記憶體,用于存放局部變量、函數的參數等,對于函數調用時現場的保護,也會用到棧記憶體,比如儲存函數的傳回位址。​棧記憶體,由 CPU 的來維護,在 32 位作業系統下,由 CPU 的 EBP 和 ESP 兩個寄存器來維護。​​

​        ​堆記憶體,是程式員通過特定的函數來申請的,比如 malloc 和 new 等函數。堆記憶體申請後由程式員來釋放。而 棧記憶體 随着函數的傳回 棧記憶體 也會被自動的回收。​​

​什麼是緩沖區溢出​

​        通常就是記憶體的覆寫,由于緩沖區分為 棧 和 堆,是以緩沖區溢出分為 ​棧溢出​ 和 堆溢出。因為 C/C++ 很多函數早期都不檢查記憶體邊界,所有的記憶體邊界檢查都由程式員自己去完成。這樣就有可能因為疏忽造成緩沖區的溢出。而現在,大部分操作記憶體的函數,都在之前函數的基礎上增加了安全檢查,也就比以前安全了。​

​        ​有些安全書籍認為,避免緩沖區溢出,不要使用棧記憶體,而是去使用堆記憶體,這樣的認識是錯誤的。因為堆記憶體的使用不當也會造成溢出,也是存在安全隐患的。​​

​緩沖區溢出攻擊​

​        ​緩沖區溢出攻擊的本質是資料當作代碼運作​。在有存在緩沖區溢出攻擊的程式中,攻擊者将可執行的代碼當作資料植入記憶體,再通過特定的方式使植入的資料運作,進而達到攻擊的目的。

​​題目​解析​

​        有了上面的鋪墊,就來說說題目中的内容。​

​        第一題,上面的代碼是哪種類型的溢出。在代碼中可以看出,數組 buf[9] 是一塊緩沖區,而 buf 是一個局部變量。局部變量是在棧中儲存。代碼中的 gets() 函數是接收使用者輸入的函數,但是它不對記憶體邊界進行檢查。buf[9] 的長度為 9 個位元組,但是當使用 gets() 函數擷取使用者輸入時,當超過 9 個位元組時,也會全部接收。這樣就造成了緩沖區溢出,更具體的說,就是棧溢出。​這點是 C/C++ 語言的特點,數組越界是被允許的,因為在很多程式設計中,為了存儲不定長資料,就會使用數組越界的方式。​​

​        第二題,假如 apple 的記憶體位址是 0x0012ff44,那麼給出 buf 中各個字元的位址。​變量相當于給某個記憶體首位址起了一個名字,變量的類型限制了該變量的記憶體長度​,比如 0x0012ff44 這個是一個記憶體的位址,給這個記憶體的位址起一個名字叫 apple,另外 變量 apple 的類型是 int,那麼限制該變量的長度占用 4 個位元組。​

​         第二題的題目,是給出我們 apple 的位址,然後讓寫出 buf 變量的位址。這裡就又需要了解兩個知識。首先,局部變量是在棧位址中這個是已知的,而​棧位址的增長方向是由高到低的​。第二,​在 C 語言中,函數内部定義的局部變量,會按照變量定義的先後順序來配置設定棧中的記憶體位址​。那麼,​在代碼中,先定義的 apple ,後定義的 buf 變量。那麼,apple 的位址就比 buf 的位址要高(大、上)​,如圖。

小議緩沖區溢出

​        知道上面兩點以後,那麼 buf 的位址到底是多少呢?還是先來說說 apple 實際占用的位址,apple 變量的位址是 0x0012ff44,這個位址其實是 apple 變量的首位址,因為 0x0012ff44 隻代表一個位元組的記憶體空間,而 apple 是 int 類型的變量,占用 4 個位元組,那麼 apple 實際占用的是 0x0012ff44、0x0012ff45、0x0012ff46 和 0x0012ff47 四個記憶體空間,也就是 4 個位元組。而 apple 就是首位址就是 0x0012ff44。​

​        再說 buf 變量,buf 的定義為 char buf[9],則說明 buf 占 9 個位元組,而​ buf 在 apple 之後定義的,那麼 buf 在棧記憶體中的位址一定是小于 apple 的位址的​。那是不是隻要用 apple 的位址減去 9 就是 buf 的位址呢?其實還不是。​雖然 buf 占 9 個位元組,但是在 32 位的 CPU 中,記憶體中的資料一般是按照 4 個位元組對齊的(32 位剛好 4 個位元組)​。那麼,​也就是通過 0x0012ff44 - 0xC 就是 buf 的首位址​。記憶體結構如下圖。​

小議緩沖區溢出

​        在上圖中,​标注為紅色的部分,就是 buf 變量的記憶體​,​标注為綠色的部分,則是 apple 變量的記憶體​。其中的​白色記憶體,就是被用來對齊的記憶體​。這樣是不是浪費了記憶體。是的!在 32 位系統下,記憶體按 4 位元組對齊,CPU 通路速度是最快的。是以,浪費 3 個位元組去進行記憶體對齊,進而換取 CPU 讀取的速度更快,是劃得來的。在計算機算法中,經常提到兩句話,“用空間換時間”和“用時間換空間”,這顯然是“用空間換時間”的情況。從上面的圖可以看出,buf 的起始位址是 0x0012ff38。​

​        第三題,是要讓程式輸出“hello world”這個字元串。但是從代碼中來看,隻有在 apple 等于 0x64636261 的時候,才會輸出"hello world"字元串。而整個代碼中就沒有對 apple 進行指派的代碼。而且 0x64636261 又是什麼?在第三題的題目中給出提示,0x61 代表小寫字母 a 的 ASCII 碼,0x62 代表小寫字母 b 的 ASCII 碼。那麼,也就是說讓 apple 中填充為字母 abcd 即可。看下圖。​

小議緩沖區溢出

​        隻要我們在給 buf 通過 gets 指派時,輸入的内容超過 9 個字元,去覆寫其後面的記憶體即可。那麼要輸入多少個字元呢?​buf 的長度是 9 個位元組,對齊的位元組是 3 個位元組,apple 的長度是 4,那麼一共輸入 16 個字元即可,前 12 個随便輸入,最後 4 個輸入 abcd 即可​。​

​        等等,代碼中 apple == 0x64636261,看起來 apple 比較的是 dcba,但是為什麼輸入的是 abcd 呢?這個是​位元組順序​的問題,這裡不展開讨論,隻要了解了位元組序的問題,就可以了解了,而位元組序在開發網絡程式和進行逆向分析時,也算是基礎的基礎。​

​示範​

​        這個程式,我使用 XP + VC6 來進行示範。為什麼使用 VC6,因為在新版的 VS 中,已經沒有 gets 函數了,因為它不安全,是以被丢棄了。​

​        把上面的代碼錄入 VC6 中,然後使用 DEBUG 進行編譯(Release編譯的話,生成的二進制會被優化,記憶體結構不明顯,溢出的方式也不同,由于是試題,用最簡單的方式表明問題即可)。​

​        編譯後,在 gets() 的位置設定斷點,然後打開“watch”視窗,來看一下 apple 和 buf 的記憶體位址,如下圖。​

小議緩沖區溢出

​        可以看出,​apple 的位址是 0x0012ff7c,buf 的記憶體位址是 0x0012ff70​。是不是有疑惑?​跟題目中的位址不同​!别急!​相同的程式在不同的作業系統(比如,XP 和 Win7)上變量的記憶體位址是不同的,甚至在更新檔不同的系統(XP SP2 和 XP SP3)上也可能是不同的。​但是,我們注意兩點,​第一,apple 的位址比 buf 的位址大,第二,apple 的位址和 buf 的位址差 0xC。​隻要憑這兩點來看,和我們前面分析的是相同的。​

​        接着打開“memory”視窗,來看記憶體,如下圖。​

小議緩沖區溢出

​        接着,在 if 的位置處下斷點,然後讓程式運作起來,我們就可以進行輸入了,如下圖。​

小議緩沖區溢出

​        我這裡輸入了 12 個 1,因為前 12 個字元随便輸入,然後輸入了 abcd,輸入完成後按下回車,我們在 if 位置處設定的斷點被斷住了,此時觀察記憶體,如下圖。​

小議緩沖區溢出

        ​從上圖可以看到,在 0x0012ff7c 的位置處,也就是 apple 所在的棧空間中,被填充了0x61、0x62、0x63 和 0x64。雖然程式中沒有任何位置給 apple 變量指派,但是我們通過溢出的方式覆寫了 apple 的記憶體位址,成功的對它進行了指派。讓程式運作起來,觀察程式的運作,如下圖。​

小議緩沖區溢出

​        可以看到,字元串“hello world”被輸出了。​

繼續閱讀