作者:信息安全專家李泉(liquan165)
愛好終端安全的朋友們大家好。最近小編在工作中遇到了許多基於堆棧的緩衝區溢出的例子,而一直沒有一個時間記錄下來分享給大家,所以我決定寫一個簡單的BLOG讓我們討論一下這個老生常談的話題。在開始棧溢出原理分析之前,我們先看一些基礎知識。
0x1 什麼是棧
堆棧是一種具有一定規則的數據結構,我們可以按照一定的規則進行添加和刪除數據。它使用的是後進先出的原則。在x86等彙編集合中堆棧與彈棧的操作指令分別為:
PUSH:將目標內存推入棧頂。
POP:從棧頂中移除目標。
下面我們展示的是一段普通C程序的內存佈局,包括堆棧,他們常被用於函數的調用與返回。
TEXT:包含了要執行的程序代碼。
Data:包含程序需要的全局數據、資源等。
Stack:包含函數的輸入參數,返回地址以及保存函數的局部變量等。Stack是後進先出的結構。隨著函數的調用,它在內存中(從高地址到低地址)向下尋址。
Heap:保存所有動態分配的內存。每當我們用malloc分配獲取內存指針時,這個地址就是從堆中分配的。
在基於棧的緩衝區溢出的情況下,我們要重點關注EBP、EIP和ESP這三個寄存器。函數中,EBP指向棧底的高地址,ESP寄存器指向棧頂的低地址。
當執行一個函數的時候,相關的參數以及局部變量等等都會被記錄在ESP、EBP中間的區域。一旦函數執行完畢,相關的棧幀就會從堆棧中彈出,然後從預先保存好的上下文中進行恢復,以便保持堆棧平衡。CPU必須要知道函數調用完了之後要去哪裡執行(EIP指向),這往往從堆棧彈出的過程中進行EIP賦值的。
為了便於理解,我們假設有一個main函數調用func()函數的程序。程序運行之後,調用func函數,將所需要的參數,全部堆入棧中,然後是返回值入棧此時的棧內分佈如下:
在func執行完成之後,相應的棧幀被彈出,此時存儲返回值的地址被加載到了EIP寄存器中以繼續執行main中剩餘的部分。而我們的目的就是控制這個返回值,這樣我們就能劫持func返回到指定的惡意代碼中去。
0x2 堆棧溢出
請看如下C代碼:
<code>#include <string.h>/<code>
<code>#include <stdio.h>/<code>
void function2() {
printf(“Execution flow changed\n”);
}
void function1(char *str){
char buffer[5];
strcpy(buffer, str);
}
void main(int argc, char *argv[])
{
function1(argv[1]);
printf(“Executed normally\n”);
}
1)Main函數調用function1並打印“Executed normally”消息。
2)function1創建了長度為5的一塊緩衝區,並且複製Main函數傳遞過來的字符串
3)function2打印了“Execution flow changed”,程序中的其它位置沒有調用過function2函數。
我們的目標是通過以上程序,傳入一個特定的參數,來控制整個程序流執行function2函數。下面我們開始對這段代碼進行編譯。
gcc -g -fno-stack-protector -z execstack -o bufferoverflow overflow.c
-g:告訴GCC編譯器將調試信息以及符號等編譯到程序中。
-fno-stack-protector:關閉堆棧保護機制。
-z execstack:打開堆棧可執行機制。(關閉堆棧執行保護)
下面我們運行程序。如圖
下一步使用AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA作為參數來破壞這個程序,這個程序會因為堆棧被破壞而崩潰。
我們打開GDB來分析一下程序崩潰的原因。使用GDB打開崩潰的二進制文件。使用list選項來顯示源代碼,然後在函數中調用,在strcpy和函數返回的地方添加斷點。
在反覆測試以及運行,不斷的變換參數內容,並試圖找到存儲崩潰地址AAAA(0x41414141)的位置。函數調用時,EBP與ESP的內容分別是:
strcpy執行過後,EBP與ESP的內容是:
函數退出時EBP與ESP為:
結合之前我們看過的堆棧分佈圖,返回地址應該是在EBP下方。在計算出EBP以及返回地址後,我們可以嘗試劫持程序執行的位置。
下面我們尋找function2的地址來劫持執行。要獲取function2的起始地址,請使用以下命令。
disass function2
我們要知道,本例我們使用的是基於INTEL架構的VM虛擬機,使用的是小字節序,所在我們生成我們需要的載荷(PAYLOAD)之前,將所有字節反轉過來。現在使用PYTHON來生成我們的有效載荷,並使用生成的字符串作為程序參數的內容。
run $(python -c 'print "A"*17 + "\\x1b\\x84\\x04\\x08"')
運行,function2函數被執行,執行流被更改。
至此,我們成功的利用了堆棧緩衝區溢出。
作者|李泉(liquan165) 梆梆安全研究院高級安全研究員
主要研究領域|車聯網、工控、物聯網安全、終端安全等
閱讀更多 黑客李泉 的文章