堆內存管理機制
當前針對各大平臺主要有如下幾種堆內存管理機制:
dlmalloc
– General purpose allocator
ptmalloc2
– glibc
jemalloc
– FreeBSD
and Firefox
tcmalloc
– Google
libumem
– Solaris
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
的管理:
主線程首次調用malloc
時glibc 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
.需要更多堆空間時通過擴展sbrk
的heap segment
,直到它碰到內存mapping
區域界限.
2. main arena
的arena header
並不是sbrk heap segment
的一部分,而是一個全局變量屬於libc.so
的data segment
.
thread arena
只含有一個malloc_state
,卻有多個heap_info
,由於新heap segments
是mmap
分配的,在內存佈局上並不相鄰.
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
時會把下個chunk
的pre_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,bin
1爲unsorted bin
.bin
2到63爲small bin
.bin
64到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 bin
的chunk
的P
(未使用標誌位)總是設置爲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 bin
中chunk
的大小可以不一樣,但處於某個給定的範圍;二是large chunk
可以添加,刪除在large bin
的任何一個位置.
2. 前32個large bin
以64字節爲間隔,即第一個large bin
中chunk size
爲512~575字節,第二個large bin
中chunk 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 bin
和large bin
的指針指向自己,代表這些bin
爲空
unlink
/* 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
)原則
- 分配兩個
fastbin
- 利用堆溢出能夠覆蓋位於高地址的
fd
使其指向fake_chunk
- 構造
fake_chunk
- 進行分配達到任意地址寫的目的
#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
- 分配多個(一般3個可以)
chunk
,然後free
- 再分配兩個內存之和比之前小的
- 通過寫構造
fake_chunk
- 後面就可以類似堆溢出的
unlink
uaf
利用方式
1. free
之後malloc
,write
造fake_chunk
2. free
之後read
來leak
基址
部分house of
系列
house of spirit
free
一個假的fastbin
堆塊,下次malloc的時候就會返回該假堆塊
1. 構造假的堆塊,在chunk
的size
位僞造
2. free fastbin
會對下一個chunk
的size
進行check
,僞造下一個chunk
的size
,所以必須爲可控區域
3. malloc
之後就可以任意地址寫
house of force
libc
的堆管理在malloc
的時候默認top chunk
的size
合法.
1. 可以修改top chunk
的size
爲大數(0xffffffff
,x86
)
2. leak heapbase
3. malloc
到任意地址
比如:top chunk
的size
修改成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
- 通過
off by one
把最後的chunk
pre_inuse
標誌位置零,要求chunk
的size
必須要是0x100
的倍數,要不然會check
下一個chunk
失敗或和top chunk
進行合併操作的時候失敗. - 再僞造一個
chunk
,使free
最後的chunk
時會和僞造的chunk
還有top chunk
進行unlink
操作,合併成一個top chunk
,將top chunk
設置成僞造chunk
的地址.對top chunk
進行讀寫
其他
house of mind
house of prime
house of lore
house of orange
off-by-one
一個字節溢出被稱爲off-by-one
因爲堆以要求的size+0x4bit
(x86
)大小分配.可以覆蓋inuse
位.
堆塊以8bit
對齊(x64
爲16bit
).malloc(1020)
,1020+8=1028
,不滿足8bit
對齊,實際只會分配1020+4=1024bit
,多出的4bit
由下一chunk
的prev_size
提供.
chunk overlapping
針對目標堆塊通過操作使目標堆塊重新分配到我們控制的新堆塊中,就可以對目標堆塊進行任意讀寫.
unlink
利用效果其實和溢出造成的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
爲一個size
爲x
的fake chunk
,再次分配unsorted_chunks (av)->bk
=
bck
會改寫unsortbin
鏈表頭的bk
,此時再分配x-4
大小的內存即可返回fakechunk
.
堆噴射
進行大量重複的內存申請,將shellcode
等數據佈局在需要的內存空間,從而繞過ASLR
保護的攻擊技術