二進(jìn)制安全之堆溢出(系列)——堆基礎(chǔ) & 結(jié)構(gòu)(一)
二進(jìn)制安全之堆溢出(系列)第二期來啦
鑒于本期干貨夠多
知了姐怕大家一時間消化不了,特意幫大家拆分成了四節(jié)內(nèi)容以下為“堆基礎(chǔ) & 結(jié)構(gòu)”第一節(jié)

堆基礎(chǔ)
堆的概念
在程序運行過程中,堆可以提供動態(tài)內(nèi)存的分配,允許程序申請大小未知的內(nèi)存。
堆其實就是在程序虛擬地址空間的一塊連續(xù)的線性區(qū)域,它由低地址向高地址生長。
我們一般稱管理堆的那部分程序為堆管理器。
堆管理器位于用戶程序和內(nèi)核中間,主要負(fù)責(zé) :
double free : 當(dāng)p已經(jīng)被釋放后再次釋放,造成亂七八糟的現(xiàn)象。
malloc
free
請求堆
響應(yīng)用戶的申請內(nèi)存請求,向操作系統(tǒng)申請內(nèi)存,然后返回給用戶程序。為了保持內(nèi)存管理的高效性,內(nèi)核一般會預(yù)先分配很大的一塊連續(xù)的內(nèi)存。
釋放堆
管理用戶釋放的內(nèi)存。用戶釋放的內(nèi)存并不是直接返還給操作系統(tǒng),而是由堆管理器進(jìn)行管理。這些釋放的內(nèi)存可以用來響應(yīng)用戶新申請的內(nèi)存的請求。
堆的歷史
Linux中早期的堆分配和回收由Doug lea實現(xiàn),但它在并行處理多個線程時,會共享進(jìn)程的堆內(nèi)存空間。因此為了安全性,一個線程使用堆時,會進(jìn)行加鎖。
然而,加鎖會導(dǎo)致其他線程無法使用堆,降低了內(nèi)存分配和回收的高效性。在多線程使用時,沒能正確控制,也可能引起內(nèi)存分配和回收的正確性。
Wolffram Gloger在Doug Lea的基礎(chǔ)上進(jìn)行改進(jìn)使其可以支持多線程,這個堆分配器就是ptmalloc。在glibc-2.3.x之后,glibc中集成了ptmalloc2。ptmalloc2主要通過malloc/free函數(shù)來分配和釋放內(nèi)存塊。
堆的實現(xiàn)
dlmalloc : Genral purpose allocator
ptmalloc2 : glibc
jemalloc : Freebsd and Firefox
tcmalloc : Google
libumen : Solaris
主要以ptmalloc2中堆的實現(xiàn)為主
內(nèi)存管理
只有當(dāng)真正訪問一個地址的時候,系統(tǒng)才會在虛擬內(nèi)存和物理頁面的映射關(guān)系。
所以操作系統(tǒng)已經(jīng)給程序分配了很大的一塊內(nèi)存,但是這開內(nèi)存其實只是虛擬內(nèi)存。只有當(dāng)用戶使用到相應(yīng)的內(nèi)存時,系統(tǒng)才會真正分配物理內(nèi)存給用戶使用。
系統(tǒng)調(diào)用
malloc和free在動態(tài)申請或釋放內(nèi)存時,主要是調(diào)用(s)brk和mmap,unmmap函數(shù)實現(xiàn)的。
(s)brk函數(shù)機制
# include <stdio.h> # include <unistd.h> # icclude <sys/types.h> int main() { void *cuur_bkr,*tmp_brk = NULL; printf("%d\n",getid()); tm_brk = curr_brk = sbrk(0);//給當(dāng)前程序一個brk printf("%p\n",curr_brk); getchar(); brk(curr_brk+4096);//設(shè)置結(jié)尾位置,即分配了4096字節(jié)的堆塊 curr_brk=sbrk(0); printf("%p\n",curr_brk); getchar(); brk(tmp_brk); curr_brk=sbrk(0); printf("%p\n",curr_brk); getchar(); return 0; }
初始時,堆的起始地址start_brk以及堆的當(dāng)前末尾brk指向同一地址。根據(jù)是否開啟ALSR,兩者的具體位置會有所不同。
不開啟ASMR時,start_brk以及brk會指向data/bss段的結(jié)尾。
開啟ASMR時,start_brk以及brk也會指向同一位置,只是這個位置是在data/bss段結(jié)尾后的隨機偏移處。
sbrk創(chuàng)建的chunk緊鄰數(shù)據(jù)段
mmap函數(shù)機制
malloc會使用mmap來創(chuàng)建獨立的匿名映射段。
匿名映射的目的主要是可以申請以0填充的內(nèi)存,并且這塊內(nèi)存僅被調(diào)用進(jìn)程所使用,這塊內(nèi)存為系統(tǒng)隨機分配。
munmap用于釋放內(nèi)存。
mmap創(chuàng)建的chunk緊鄰libc
data/bss
bss段通常是指用來存放程序中未初始化的全局變量的一塊內(nèi)存區(qū)域。
data段通常是指用來存放程序中已初始化的全局變量的一塊內(nèi)存區(qū)域。
多線程支持
在原來的dlmalloc實現(xiàn)中,當(dāng)兩個線程同時要申請內(nèi)存時,只有一個線程可以進(jìn)入臨界區(qū)申請內(nèi)存,而另外一個線程必須等待直到臨界區(qū)中不再有線程。
這是因為所有的線程共享一個堆。
在glibc和ptmalloc實現(xiàn)中,支持了多線程的快速訪問,在新的實現(xiàn)中,所有的線程共享多個堆。
堆數(shù)據(jù)結(jié)構(gòu)
宏觀結(jié)構(gòu):包括堆的宏觀信息,通過這些數(shù)據(jù)結(jié)構(gòu)索引堆的基本信息
宏觀結(jié)構(gòu)主要是堆塊之間的連接
微觀結(jié)構(gòu):主要用于處理堆的分配與回收中的內(nèi)存塊
malloc & free
宏觀結(jié)構(gòu)
arena & main_arena
主線程對應(yīng)main_arena,管理所有堆塊的結(jié)構(gòu)體
多線程的子線程對應(yīng)arena,存在于線程的控制塊plt中
不是每個線程都會有對應(yīng)的arena
因為每個系統(tǒng)的核數(shù)有限,當(dāng)線程數(shù)大于核數(shù)的二倍時,就必然有線程處于等待狀態(tài),所以沒有必要為每個線程分配一個arena
32bit --> arena_num = 2 * core
64bit --> arena_num = 8 * core
chunk_size的倒數(shù)第三個標(biāo)志位NON_MAIN_ARENA,多線程時為1,主線程為0
子線程的堆和主線程的堆不一樣
每個線程會預(yù)分配一個堆空間
線程會從這個對空間創(chuàng)建top_chunk和堆塊
當(dāng)malloc的空間超過預(yù)分配的大小,會回到main_arena之前再次分配一個空間
如果線程的堆存在溢出,可以之前的chunk越界寫堆的arena結(jié)構(gòu)
定位子線程的chunk的技巧
向子線程的堆塊輸入特殊值:"0xdeadbeef"
在gdb使用 search -4 0xdeadbeef
搜索出來的地址即堆的地址
多線程利用思路
在子線程中找到堆空間的地址空間A
在A中找到恢復(fù)線程的arena的結(jié)構(gòu)
通過arena的結(jié)構(gòu)嘗試堆利用
top_chunk
當(dāng)一個chunk處于一個arena的最頂部(最高內(nèi)存地址)的時候,稱之為top_chunk
當(dāng)系統(tǒng)當(dāng)前所有的bin都無法滿足用戶請求的內(nèi)存大小的時候,將此chunk分配給用戶使用
main_arena ---> sbrk
thread arena ---> mmap
如果top_chunk比用戶請求的大小要大的話,就將該top_chunk分為兩部分
用戶請求的chunk
剩余的部分成為新的top_chunk
否則需要擴展heap獲分配新的heap,原來的top_chunk劃入unsortedbin
top_chunk漏洞利用
當(dāng)當(dāng)前的top_chunk的空間不夠的時候,系統(tǒng)就會新創(chuàng)建一個top_chunk
原來的top_chunk被分配到到unsortedbin里面
在題目中沒有free函數(shù)的時候,則無法將塊進(jìn)入bin鏈
off by one --> 在top_chunk之上構(gòu)建一個0x88的堆塊,改寫top_chunk的size大小
// [漏洞學(xué)名]:house of orange
bins
作用:管理free的malloc_chunk
種類:按照free的chunk大小劃分
fastbin :0x20-0x80 :注意fastbin不屬于bins,是ptmalloc單獨用來管理0x20-0x80的堆塊的數(shù)據(jù)結(jié)構(gòu),如果free的chunk大小在0x20-0x80之間,會優(yōu)先進(jìn)入fashbin,
smallbin :0x20-0x400
unsortedbin : free掉的chunk優(yōu)先進(jìn)入unsortedbin,除了fastbin管理的堆塊
存在整理過程,將所有放在unsortedbin鏈上的堆塊按照大小整理到其它鏈上
將fastbin上的碎片整理到unsorted,再有unsorted整理到其他bin鏈
largebin :0x400以上
對于small bins,large bins,unsorted bins來說,ptmalloc將它們維護在同一個數(shù)組中,對應(yīng)的數(shù)據(jù)結(jié)構(gòu)在malloc_state中
#define NBINS 128 // bins總共有128個,除了fastbin
mchunkptr bins[NBINS * 2 - 2] //mchunkptr 是指向chunk頭的指針,bin = fd+bk
管理流程
malloc/free --> glibc --> arena --> fastbin/bins -->smallbin/largebin/unsortedbin
從glibc找到main_arena
在main_arena的管理結(jié)構(gòu)體malloc_state通過固定偏移中找到fastbinsY[NFASTBINS],用以管理fastbin。
找到bins[NBINS * 2 - 2],用以管理unsortedbin。
bin的放置順序
索引為1的是unsortedbin,這里面的chunk沒有進(jìn)行排序,比較雜亂。
索引從2到63的bin稱為small bin,同一個small bin鏈表中的chunk的大小相同。兩個相鄰索引的small bin鏈表中的chunk大小為2個機器字節(jié),即32-->4字節(jié),64-->8字節(jié)。
索引從64到126的bin被稱為large bin。large bins中的每一個bin都包含一定范圍內(nèi)的chunk,其中的chunk按fd指針的順序從大到小排列,最靠近bin頭的越大,相同大小的chunk按照最近使用順序排列。
任意兩個物理相鄰的空閑chunk不能在一起,否則會合并。
free之后的chunk,與top_chunk相鄰的,會與top_chunk合并,不與之相鄰的,會根據(jù)其大小進(jìn)入到不同的bin
小的進(jìn)入fastbin,大的進(jìn)入unsortedbin
此時,釋放掉的chunk不會馬上歸還系統(tǒng),ptmalloc會統(tǒng)一管理heap和mmap映射區(qū)域的空閑的chunk。
當(dāng)用戶再一次請求分配內(nèi)存時,ptmalloc分配器會試圖在空閑的chunk中挑選一塊合適的給用戶,這樣可以避免頻繁的系統(tǒng)調(diào)用,減少內(nèi)存分配的開銷。
需要注意的是,并不是所有的chunk被釋放之后立即放到bin中。ptmalloc為了提高分配的速度,會把一些小的堆塊先放到fast bin的容器內(nèi)。而且fast bin容器中的chunk的使用標(biāo)記總是被置為1的,所以不會自動合并。
后續(xù)內(nèi)容請鎖定第二期哦~~
