棧溢出原理淺析

作者:信息安全專家李泉(liquan165)

愛好終端安全的朋友們大家好。最近小編在工作中遇到了許多基於堆棧的緩衝區溢出的例子,而一直沒有一個時間記錄下來分享給大家,所以我決定寫一個簡單的BLOG讓我們討論一下這個老生常談的話題。在開始棧溢出原理分析之前,我們先看一些基礎知識。

0x1 什麼是棧

堆棧是一種具有一定規則的數據結構,我們可以按照一定的規則進行添加和刪除數據。它使用的是後進先出的原則。在x86等彙編集合中堆棧與彈棧的操作指令分別為:

PUSH:將目標內存推入棧頂。

POP:從棧頂中移除目標。

下面我們展示的是一段普通C程序的內存佈局,包括堆棧,他們常被用於函數的調用與返回。

棧溢出原理淺析

一個普通的C程序的內存分佈

TEXT:包含了要執行的程序代碼。

Data:包含程序需要的全局數據、資源等。

Stack:包含函數的輸入參數,返回地址以及保存函數的局部變量等。Stack是後進先出的結構。隨著函數的調用,它在內存中(從高地址到低地址)向下尋址。

Heap:保存所有動態分配的內存。每當我們用malloc分配獲取內存指針時,這個地址就是從堆中分配的。

在基於棧的緩衝區溢出的情況下,我們要重點關注EBP、EIP和ESP這三個寄存器。函數中,EBP指向棧底的高地址,ESP寄存器指向棧頂的低地址。

棧溢出原理淺析

ESP、EBP在棧中的位置

當執行一個函數的時候,相關的參數以及局部變量等等都會被記錄在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:打開堆棧可執行機制。(關閉堆棧執行保護)

下面我們運行程序。如圖

棧溢出原理淺析

以AAAA作為參數運行程序

下一步使用AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA作為參數來破壞這個程序,這個程序會因為堆棧被破壞而崩潰。

棧溢出原理淺析

程序崩潰

我們打開GDB來分析一下程序崩潰的原因。使用GDB打開崩潰的二進制文件。使用list選項來顯示源代碼,然後在函數中調用,在strcpy和函數返回的地方添加斷點。

棧溢出原理淺析

GDB調試

在反覆測試以及運行,不斷的變換參數內容,並試圖找到存儲崩潰地址AAAA(0x41414141)的位置。函數調用時,EBP與ESP的內容分別是:

棧溢出原理淺析

EBP、ESP

strcpy執行過後,EBP與ESP的內容是:

棧溢出原理淺析

函數退出時EBP與ESP為:

棧溢出原理淺析

結合之前我們看過的堆棧分佈圖,返回地址應該是在EBP下方。在計算出EBP以及返回地址後,我們可以嘗試劫持程序執行的位置。

下面我們尋找function2的地址來劫持執行。要獲取function2的起始地址,請使用以下命令。

disass function2

棧溢出原理淺析

獲取function2的起始地址

我們要知道,本例我們使用的是基於INTEL架構的VM虛擬機,使用的是小字節序,所在我們生成我們需要的載荷(PAYLOAD)之前,將所有字節反轉過來。現在使用PYTHON來生成我們的有效載荷,並使用生成的字符串作為程序參數的內容。

run $(python -c 'print "A"*17 + "\\x1b\\x84\\x04\\x08"')

運行,function2函數被執行,執行流被更改。

棧溢出原理淺析

運行結果

至此,我們成功的利用了堆棧緩衝區溢出。

棧溢出原理淺析

作者|李泉(liquan165) 梆梆安全研究院高級安全研究員

主要研究領域|車聯網、工控、物聯網安全、終端安全等


分享到:


相關文章: