pwn-堆溢出(ptmalloc2)

堆內存管理機制

當前針對各大平臺主要有如下幾種堆內存管理機制:
dlmallocGeneral purpose allocator
ptmalloc2glibc
jemallocFreeBSD and Firefox
tcmallocGoogle
libumemSolaris

linux平臺*malloc本質上都是通過系統調用brk或者mmap實現

調用malloc之前程序進程沒有heap segment.在主線程中調用malloc之後,系統分配堆棧在數據段上,說明是通過brk系統調用實現.

Arena

Arena數量限制:
For 32 bit systems:
Number of arena = 2 * number of cores + 1.
For 64 bit systems:
Number of arena = 8 * number of cores + 1.

Arena的管理:
主線程首次調用mallocglibc malloc會爲它分配一個main arena
glibc malloc能維護的arena達到上限後需要共享使用分配好的arena
1. glibc malloc首先循環遍歷所有可用的arena,在遍歷的過程中,它會嘗試lock,該arena當前對應的線程未使用堆內存則可lock,然後就將arena返回給用戶,該arena被兩個線程共享使用。
2. 如果沒能找到可用的arena,那麼線程3的malloc操作將被阻塞到有可用的arena爲止.
3. 共享後線程3再次調用malloc,glibc malloc就會嘗試最近訪問的arena,如果此時arena可用,就使用,否則就將線程3阻塞到arena再次可用.

堆管理數據結構

heap_info:即Heap Header.
一個thread arena(不包含main thread)可以包含多個heap,每個heap分配一個heap header.在當前heap不夠用的時候,malloc會通過系統調用mmap申請新的堆空間,新的堆空間會被添加到當前thread arena.

typedef struct _heap_info
{
    mstate ar_ptr; /* Arena Header */
    struct _heap_info *prev; /* Previous heap. */
    size_t size; /* Current size in bytes. */
    size_t mprotect_size; /* Size in bytes that has been mprotected
    PROT_READ|PROT_WRITE.*/
    /* Make sure the following data is properly aligned, particularly
    that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
    MALLOC_ALIGNMENT. */
    char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info; 

malloc_state:即Arena Header.
每個thread只含有一個Arena Header.Arena Header包含bins,top chunk以及最後一個remainder chunk等.

struct malloc_state
{
    /* Serialize access.*/
    mutex_t mutex;/* Flags (formerly in max_fast).*/
    int flags;/* Fastbins */
    mfastbinptr fastbinsY[NFASTBINS];/* Base of the topmost chunk -- not otherwise kept in a bin */
    mchunkptr top;/* The remainder from the most recent split of a small request */
    mchunkptr last_remainder;/* Normal bins packed as described above */
    mchunkptr bins[NBINS * 2 - 2];/* Bitmap of bins */
    unsigned int binmap[BINMAPSIZE];/* Linked list */
    struct malloc_state *next;/* Linked list for free arenas.*/
    struct malloc_state *next_free;/* Memory allocated from the system in this arena.*/
    INTERNAL_SIZE_T system_mem;
    INTERNAL_SIZE_T max_system_mem;
}; 

malloc_chunk:即Chunk Header.
一個heap被分爲多個chunk,用戶調用malloc(size)傳遞的size參數決定chunk的大小(但不相等).每個chunk對應一個malloc_chunk

struct malloc_chunk {
    /* #define INTERNAL_SIZE_T size_t */
    INTERNAL_SIZE_Tprev_size;/* Size of previous chunk (if free).*/
    INTERNAL_SIZE_Tsize;/* Size in bytes, including overhead. */
    struct malloc_chunk* fd;/* double links -- used only if free. 這兩個指針只在free chunk中存在*/
    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;
}; 

TIPS:
1. main thread不含有多個heaps.需要更多堆空間時通過擴展sbrkheap segment,直到它碰到內存mapping區域界限.
2. main arenaarena header並不是sbrk heap segment的一部分,而是一個全局變量屬於libc.sodata segment.

thread arena只含有一個malloc_state,卻有多個heap_info,由於新heap segmentsmmap分配的,在內存佈局上並不相鄰.
libc malloc將後一個heap_info結構體的prev成員指向前一個heap_info結構體的ar_ptr成員,第一個heap_info結構體的ar_ptr成員指向malloc_state

chunk

chunk總共分爲4類
1. allocated chunk
2. free chunk
3. top chunk
4. Last remainder chunk

先將這4類chunk簡化爲allocated chunk以及free chunk
在32位系統中,chunk永遠是8的倍數,在64位系統中.chunk是16的倍數.實際malloc時會把下個chunkpre_size用上

這裏寫圖片描述

PREV_INUSE(P):表示前一個chunk是否爲allocated.
IS_MMAPPED(M):表示當前chunk是否是通過mmap系統調用產生.
NON_MAIN_ARENA(N):表示當前chunk是否是thread arena

Top Chunk

當一個chunk處於一個arena的最頂部的時候,就稱之爲top chunk.
chunk並不屬於任何bin,而是在系統當前的所有free chunk都無法滿足用戶請求的內存的時候,如果top chunk的大小比用戶請求大,就將該top chunk分作用戶請求的chunk和新的top chunk,否則就擴展heap或分配新的heap.

Last Remainder Chunk

當用戶請求一個small chunk,且該請求無法被small bin,unsorted bin滿足,就通過binmaps遍歷bin查找最合適的chunk,如果該chunk有剩餘部分就將該部分變成一個新的chunk加入到unsorted bin中,並將該chunk變成last remainder chunk.

bin

系統針對不同大小的free chunk,將bin分爲了
1. Fast bin
2. Unsorted bin
3. Small bin
4. Large bin

glibc中用於記錄bin的數據結構有兩種
1. fastbinsY:一個數組,記錄所有的fast bins.10個
2. bins:一個數組,記錄除fast bins之外的bins.共126個bins,bin1爲unsorted bin.bin2到63爲small bin.bin64到126爲large bin

fast bin

chunk size爲16到80字節的chunk就叫做fast chunk,10個fast bin中所包含的fast chunk size是按照步進8字節排列的(32位).64位爲32到160
默認情況下,fastbin數組的最後3個是不會存儲數據的
系統將屬於fast binchunkP(未使用標誌位)總是設置爲1,避免自動合併操作

unsorted bin

釋放較小或較大的chunk的時候,如果系統沒有將它們添加到對應的bins,系統就將這些chunk添加到unsorted bin中,利用unsorted bin,可以加快內存的分配和釋放操作.

unsorted bin的特性
1. unsorted bin是一個由free chunks組成的雙向鏈表
2. chunk size:對chunk的大小並沒有限制

small bin

小於512字節的chunk稱之爲small chunk,small bin就是用於管理small chunk的.

small bin的特性如下:
1. small bin是一個對應free chunk組成的雙向鏈表.small bin採用FIFO算法:內存釋放操作就將釋放的chunk添加到鏈表的front end,分配操作就從鏈表的rear end中獲取chunk
2. chunk size:一個small bin所有chunk大小一樣,16到512字節
3. free:free時相鄰的free chunk需要進行合併操作合併成一個大的free chunk加入unsorted bin

large bin

大於512字節的chunk稱之爲large chunk

large bin的特性
1. large bin類似於small bin,需要注意兩點:一是同一個large binchunk的大小可以不一樣,但處於某個給定的範圍;二是large chunk可以添加,刪除在large bin的任何一個位置.
2. 前32個large bin以64字節爲間隔,即第一個large binchunk size爲512~575字節,第二個large binchunk size爲576~639字節.其後16個large bin以512字節爲間隔;其後的8個bin以4096爲間隔;再其後的4個bin以32768字節爲間隔;其後的2個bin以262144字節爲間隔;剩下的chunk就放在最後一個large bin
3. 同一個large bin中的所有chunk按照chunk size進行從大到小的排列:最大的chunk放在鏈表的front end,最小的chunk放在rear end.
4. 合併操作:類似於small bin
5. malloc:chunk大於用戶請求的size,將該chunk拆分爲兩個chunk:前者返回給用戶,剩餘部分作爲新的chunk添加到unsorted bin中.小於用戶請求的size,檢索後續的large bin,glibc malloc設計了Binmap結構體來記錄了各個bin是否爲空,如果找到了非空large bin,按照上段的方法分配chunk,否則使用top chunk分配

初始化

malloc_consolidate函數對malloc_state結構體進行初始化
1. 首先判斷當前malloc_state結構體中的fast bin是否爲空,爲空對malloc_state進行初始化.
2. malloc_state的初始化操作由函數malloc_init_state(av)完成,該函數先初始化除fast bin之外的所有的bins,再初始化fast bins

初始化將所有small binlarge bin的指針指向自己,代表這些bin爲空

/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {
    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
        malloc_printerr ("corrupted size vs. prev_size");
        FD = P->fd;
        BK = P->bk;
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
        malloc_printerr ("corrupted double-linked list");
    else {
        FD->bk = BK;
        BK->fd = FD;//正常情況,後面爲大內存分配
    if (!in_smallbin_range (chunksize_nomask (P)) && __builtin_expect (P->fd_nextsize != NULL, 0)) {
        if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
            malloc_printerr ("corrupted double-linked list (not small)");
            if (FD->fd_nextsize == NULL) {
                if (P->fd_nextsize == P)
                    FD->fd_nextsize = FD->bk_nextsize = FD;
                else {
                    FD->fd_nextsize = P->fd_nextsize;
                    FD->bk_nextsize = P->bk_nextsize;
                    P->fd_nextsize->bk_nextsize = FD;
                    P->bk_nextsize->fd_nextsize = FD;
                }
            } else {
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;
            }
        }
    }
}

unlink時執行的檢查:
1. chunk size是否等於next chunk(內存意義上的)的prev_size
2. 檢查是否P->fd->bk == P && P->bk->fd == P

找一個*X=P,P->fd = X - 3,P->bk = X - 2(X爲一個指針)

unlink發生在堆塊合併之後,堆塊合併發生在free之後,條件爲free堆塊前一堆塊空閒,對應free堆塊的P標誌位.
通過heap overflow可以僞造fake chunk和下一個堆塊的chunk header,取X爲溢出堆的指針,從而利用unlink使溢出堆的堆指針指向自己附近,chunk_ychu[3] == chunk1_ychu

fastbin attack

fastbin爲單鏈表,並沒對之前提到的bk進行操作,遵循後進先出(LIFO)原則

  1. 分配兩個fastbin
  2. 利用堆溢出能夠覆蓋位於高地址的fd使其指向fake_chunk
  3. 構造fake_chunk
  4. 進行分配達到任意地址寫的目的
#define fastbin_index(sz) \
  ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
...
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
  {
    errstr = "malloc(): memory corruption (fast)";

32bit基數爲8,64bit基數爲16,無地址對齊檢測.

double free

也是利用unlink

  1. 分配多個(一般3個可以)chunk,然後free
  2. 再分配兩個內存之和比之前小的
  3. 通過寫構造fake_chunk
  4. 後面就可以類似堆溢出的unlink

uaf

利用方式
1. free之後malloc,writefake_chunk
2. free之後readleak基址

部分house of系列

house of spirit

free一個假的fastbin堆塊,下次malloc的時候就會返回該假堆塊
1. 構造假的堆塊,在chunksize位僞造
2. free fastbin會對下一個chunksize進行check,僞造下一個chunksize,所以必須爲可控區域
3. malloc之後就可以任意地址寫

house of force

libc的堆管理在malloc的時候默認top chunksize合法.
1. 可以修改top chunksize爲大數(0xffffffff,x86)
2. leak heapbase
3. malloc到任意地址

比如:top chunksize修改成0xffffffff,此時top_chunk=0x601600, malloc(0xffdffc20),0xffdffc20 < top_chunk_size,成功malloc內存,top_chunk的新地址0xffdffc20+0x601600=0x100401230, x86環境溢出,top_chunk = 0x401230.再malloc時,返回地址就是0x401238

house of einherjar

  1. 通過off by one把最後的chunk pre_inuse標誌位置零,要求chunksize必須要是0x100的倍數,要不然會check下一個chunk失敗或和top chunk進行合併操作的時候失敗.
  2. 再僞造一個chunk,使free最後的chunk時會和僞造的chunk還有top chunk進行unlink操作,合併成一個top chunk,將top chunk設置成僞造chunk的地址.對top chunk進行讀寫

其他

  1. house of mind
  2. house of prime
  3. house of lore
  4. house of orange

off-by-one

一個字節溢出被稱爲off-by-one
因爲堆以要求的size+0x4bit(x86)大小分配.可以覆蓋inuse位.
堆塊以8bit對齊(x6416bit).malloc(1020),1020+8=1028,不滿足8bit對齊,實際只會分配1020+4=1024bit,多出的4bit由下一chunkprev_size提供.

chunk overlapping

針對目標堆塊通過操作使目標堆塊重新分配到我們控制的新堆塊中,就可以對目標堆塊進行任意讀寫.

利用效果其實和溢出造成的unlink的利用效果是一致

unsortedbin attack

當需要分配的內存無法在fastbin或者smallbin找到時,glibc會從unsort bins的鏈表頭
bk開始遍歷,遍歷過程中會把unsortbin中的塊加入合適的smallbin / largebin中,如果
找到合適大小內存塊則會返回.

bck = victim->bk; 
... 
unsorted_chunks (av)->bk = bck; 
bck->fd = unsorted_chunks (av); 

利用思路:

  • 通過堆溢出覆蓋victim->bk爲要寫入的地址-4,再次分配時bck->fd = unsorted_chunks (av)
    觸發一個任意地址寫.寫入的內容是libc中的一個地址.只不過此時unsortbin被破壞,再次分
    配代碼會崩掉,所以要謹慎考慮寫入的地址,通常可以改寫global_max_fast,從而導致接下來
    所有分配都是在libc進行
  • 通過堆溢出覆蓋victim->bk爲一個sizexfake chunk,再次分配unsorted_chunks (av)->bk
    = bck會改寫unsortbin鏈表頭的bk,此時再分配x-4大小的內存即可返回fakechunk.

堆噴射

進行大量重複的內存申請,將shellcode等數據佈局在需要的內存空間,從而繞過ASLR保護的攻擊技術

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章