1. 程式人生 > >Linux 堆溢出原理分析

Linux 堆溢出原理分析

s函數 來看 size first image prev auto 什麽 address

堆溢出與堆的內存布局有關,要搞明白堆溢出,首先要清楚的是malloc()分配的堆內存布局是什麽樣子,free()操作後又變成什麽樣子。

解決第一個問題:通過malloc()分配的堆內存,如何布局?

技術分享圖片

技術分享圖片

上圖就是malloc()分配兩塊內存的情形。

其中mem指針指向的是malloc()返回的地址,pre_size與size都是4字節數據,size存放當前chunk(內存塊,本文均不翻譯)大小,pre_size存放上一個chunk大小。

因為malloc實現分配的內存空間是8字節對齊的,所以size的低3位其實沒用,就取其中一位,用來標誌前一個chunk是否被釋放即PREV_INUSE位。當前一chunk釋放,PREV_INUSE位置0,否則置1。

當malloc()分配的空間使用完畢後,將其mem指針傳給free()進行釋放。

解決第二個問題:free()對堆內存布局會產生什麽影響?
技術分享圖片

技術分享圖片

上圖的情形是,當前chunk的上一chunk被free()釋放,容易發現,當前chunk的PREV_ISUSE標誌位置0,表示前一chunk已經被釋放。

被釋放的chunk中,原先data的位置的低地址處被填入兩個指針,分別是fd和bk,它們是forward和backward單詞的縮寫,分別表示前一個free chunk和後一個free chunk的地址。這樣所有通過free()釋放的內存chunk會組成一個雙向鏈表。也因此一個chunk最小長度為16字節:2個size和2個指針。

當一個chunk被釋放時,還有一件事情要做,就是檢查相鄰chunk的是否處於釋放狀態,如果相鄰chunk空閑的話,就會進行chunk合並操作。由於每個chunk中都存放了size信息,所以很容易就找到當前chunk前後chunk的狀態。

free()裏面會調用一個unlink宏來執行合並操作:

#define unlink(P, BK, FD) {                            FD = P->fd;                                          BK = P->bk;                                          FD->bk = BK;                                         BK->fd = FD;                                   }

好了,這個宏就是堆溢出利用的關鍵。仔細閱讀這個宏其實就是在一個雙向鏈表中刪除一個結點的操作:

P->fd->bk = P->bk
P->bk->fd = P->fd

其中P代表當前被刪除結點。

解決最後一個問題:堆溢出如何利用?

首先構造一段堆溢出漏洞代碼:

int main(void)
{
    char *buff1, *buff2;
    buff1 = malloc(40);
    buff2 = malloc(40);
    gets(buff1);
    free(buff1);
    exit(0);
}

給出堆空間布局:

low address
+---------------------+   <--first chunk ptr
|     prev_size       |
+---------------------+
|     size=48         |          
+---------------------+   <--first                  
|                     |
|     allocated       |         
|      chunk          |      
+---------------------+   <--second chunk ptr                
|    prev_size        |         
+---------------------+                     
|    size=48          |         
+---------------------+   <--second                  
|     Allocated       |         
|       chunk         |     
+---------------------+      
high address

現在使用gets函數進行堆溢出,將第2塊chunk的prev_size覆蓋為任意值,size覆蓋為-4即0xfffffffc,fd位置覆蓋為exit@got-12,bk位置覆蓋為shellcode地址。

覆蓋後的堆空間布局情況:

low address
+---------------------+   <--first chunk ptr
|     prev_size       |
+---------------------+
|     size=48         |          
+---------------------+   <--first                  
|                     |
|     allocated       |         
|      chunk          |      
+---------------------+   <--second chunk ptr                
|    XXXXXXXXX        |         
+---------------------+                     
|   size=0xfffffffc   |         
+---------------------+   <--second   
|     exit@got-12     |  
|    shellcode地址    |               
|     Allocated       |         
|       chunk         |     
+---------------------+      
high address

下面看free(buff1)時發生的操作:

1.first空間即buff1被釋放掉

2.檢查上一chunk是否需要合並(這裏否)

3.檢查下一chunk是否需要合並,檢查的方法是檢查下下個chunk的PREV_ISUSE標誌位。即當前chunk加上當前size得到下個chunk,下個chunk加上下個size得到下下個chunk,因為我們設置下個chunk大小為-4,則下個chunk的pre_size位置被認為是下下個chunk的開始,下個size位置是0xfffffffc標誌未置位,被認為是free的需合並。

那麽,這裏合並用到unlink宏時出問題了,同樣對照上面圖來看:

second->fd->bk=second->bk
    /* 1.second->bk是shellcode址
       2.shellcode的地址被寫進了second->fd+12的位置
       3.second->fd是exit@got的地址-12
       4.所以second->fd+12的位置就是exit@got-12 + 12 = exit@got即got中存的exit地址
       因此exit()函數地址已經被shellcode地址替換
    */
second->bk->fd=second->fd

“shellcode的地址被寫進了second->fd+12的位置” 這句話要好好理解,為什麽second->fd->bk是second->fd+12呢? 其實second->fd指向前一chunk頭部,加12是跳過pre_size,size和fd即到達bk位置。

最後程序在執行到exit(0)語句時,由於地址被替換,shellcode執行。

Linux 堆溢出原理分析