二進制安全之堆溢出(系列)——堆基礎 & 結構(二)
哈嘍啊這里是知了堂禁衛(wèi)實驗室
今天知了姐給大家搬來《二進制安全之堆溢出(系列)第二期“堆基礎 & 結構”第二節(jié)》!!

?話不多說,直接上干貨!
? ? 微觀結構
? ? 函數(shù)執(zhí)行流程
? ? void *malloc (size_t bytes)
? ? void *__libc_malloc (size_t bytes) //對于_int_malloc做簡單封裝
? ? __malloc_hook? //類似于虛函數(shù),派生接口,指定一個malloc的方式
? ? _int_malloc(mstate av, size_t bytes) //申請內存塊的核心
? ? main_arena
? ? 集中管理bins鏈的結構體,使用含fd和bk的bin頭一對一管理各個free的chunk
? ? 分釋放配堆塊是基于main_arena來尋址的,首先找的是fastbin,其次再找bins
? ? main_arena存儲在libc上,用以管理所有bins的頭和尾,每個bins鏈頭的fd和尾的b與之連接
? ? struct malloc_state
? ? {
? ? /* Serialize access. */
? ? __libc_lock_define (, mutex);//定義了一個0x4字節(jié)的lock
? ? /* Flags (formerly in max_fast). */
? ? int flags;//0x4
? ? /* Set if the fastbin chunks contain recently inserted free blocks. */
? ? /* Note this is a bool but not all targets support atomics on booleans. */
? ? int have_fastchunks;//0x4
? ? /* Fastbins */
? ? mfastbinptr fastbinsY[NFASTBINS]; //fastbin鏈的管理頭,總共10個, 每個0x10字節(jié)
? ? /* Base of the topmost chunk -- not otherwise kept in a bin */
? ? mchunkptr top;//0x4 到此為止總共0x96字節(jié)
? ? /* The remainder from the most recent split of a small request */
? ? mchunkptr last_remainder;? ? ? ? //切割后剩下的chunk鏈接到last_remainder
? ? /* Normal bins packed as described above */
? ? mchunkptr bins[NBINS * 2 - 2];? ?// 每個bin頭有fd和bk兩個指針
? ? /* Bitmap of bins */
? ? unsigned int binmap[BINMAPSIZE];? ?//位圖,用32bit來分別表示當前bin哪個鏈上有chunk,通過按位與的方式
? ? /* Linked list */
? ? struct malloc_state *next;
? ? /* Linked list for free arenas. Access to this field is serialized
? ? by free_list_lock in arena.c. */
? ? struct malloc_state *next_free;
? ? /* Number of threads attached to this arena. 0 if the arena is on
? ? the free list. Access to this field is serialized by
? ? free_list_lock in arena.c. */
? ? INTERNAL_SIZE_T attached_threads;
? ? /* Memory allocated from the system in this arena. */
? ? INTERNAL_SIZE_T system_mem;
? ? INTERNAL_SIZE_T max_system_mem;
? ? }
? ? main_arena:用來管理整個bin鏈的結構體,總共128個bin,10個fastbin
? ? 每個bin頭可以簡化為fd和bk兩個前后項指針
? ? glibc ---> main_arena ---> 對應的bins頭的fd和bk ---> 遍歷找到對應free的chunk
? ? main_arena存放在libc中,其中存放的是每一個bin鏈的頭尾
? ? tips:如果我們能打印一個非fastbin鏈中的fd,bk,那我們就可以計算出libc的基地址libc.addr = libc_on - libc.sysbols["main_arena"] - 88
? ? main_chunk
? ? 在程序的執(zhí)行過程中,我們稱malloc申請的內存為chunk。這塊內存在ptmalloc內部用malloc_chunk結構體來表示。
? ? 當程序申請的chunk被free后,會被加入到相應的空閑管理列表中。
? ? 無論一個chunk的大小如何,處于分配狀態(tài)還是釋放狀態(tài),它們都使用一個統(tǒng)一的結構。但根據(jù)是否被釋放,結構會有所更改。
? ? struct malloc_chunk {
? ? INTERNAL_SIZE_T? ? ? mchunk_prev_size;? // 如果前面一個堆塊是空閑的則表示前一個堆塊的大小,否則無意義
? ? INTERNAL_SIZE_T? ? ? mchunk_size;? ? ? ?//當前chunk的大小,由于對齊的原因所以低三位作為flag,意義如下:
? ? /*
? ? A:倒數(shù)第三位表示當前chunk屬于主分配區(qū)(0)還是非主分配區(qū)(1)
? ? M:倒數(shù)第二位表示當前chunk是從mmap(1)[多線程]分配的,還是從brk(0)[子線程]分配的
? ? P:最低為表示前一塊是否在使用中
? ? */
? ? /*
? ? 1.真正的內存從這里開始分配
? ? 2.malloc之后這些指針沒有用,這時存放的是數(shù)據(jù)
? ? 3.只有在free之后才有效。
? ? */
? ? struct malloc_chunk* fd;? ? ? ?//當chunk空閑時才有意義,記錄后一個空閑chunk的地址
? ? struct malloc_chunk* bk;? ?//同上,記錄前一個空閑chunk的地址
? ? /* Only used for large blocks: pointer to next larger size. */
? ? struct malloc_chunk* fd_nextsize; //當前chunk為largebin時才有意義,指向比當前chunk大的第一個空閑chunk
? ? struct malloc_chunk* bk_nextsize; //指向比當前chunk小的第一個空閑堆塊
? ? };
? ? prev_size
? ? malloc(0x18)會分配0x20的內存
? ? malloc(0x19)分會配0x30的內存
? ? 如果該chunk的物理相鄰的前一地址chunk(兩個指針的地址差值為前一個chunk大小)是空閑的話,那該字段記錄的是前一個chunk的大小
? ? 否則用來存儲物理相鄰的前一個chunk的數(shù)據(jù),這里前一個chunk指的是較低地址的chunk。
? ? prev_size位可以被共享,當前的chunk, 如果不夠用就會占用下一塊chunk的prev_size
? ? size
? ? chunk1的數(shù)據(jù)有效區(qū)域覆蓋到chunk2的prev_size位,并且chunk2的size位的prev_inuse被覆蓋為0。系統(tǒng)認為chunk2之前的chunk1已經未在使用了。
? ? 當free(chunk2)的時候,系統(tǒng)會將chunk2與chunk2中prev_size大小的空間合并到bins。
? ? 我們可以通過改變chunk2的prev_size的內容,操縱向前合并的大小。
? ? 造成的問題:overlap(堆塊重疊),chunk1被釋放了,但是我們可以操縱修改它(堆利用的核心思想),從而修改bins鏈的內容,泄露其中的地址。
? ? 形成的攻擊:fastbin ---> fd ---> main_arena ---> 分配新的堆塊,我們通過修改chunk1的fd內容,達到分配任意內存的目的,造成fastbin attack。
? ? 記錄前一個chunk是否被分配。
? ? 一般來說,堆中第一個被分配的內存塊的size字段的P位都會被設置為1,以便于防止訪問前面的非法內存。
? ? 當一個chunk的size位的P位為0時,我們能通過prev_size獲取上一個chunk的大小及地址,方便進行空閑堆塊的合并。
? ? 對于fastbin的堆塊,不管前面還有沒有被分配的chunk,PREV_INUSE都為1。
? ? 64位chunk的size必須是16字節(jié)對齊
? ? 32位chunk的size必須是8 字節(jié)對齊
? ? 64位 低4位沒用 11110000
? ? 32位 低3位沒用 11111000
? ? define chunksize(p) (chunk_nomask (p) & ~(SIZE_BITS))
? ? define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
? ? NON_MAIN_ARENA記錄當前chunk是否是main_arena管理的堆塊,1表示不屬于,0表示屬于
? ? IS_MAPPED記錄當前的chunk是否是由mmap分配的。
? ? PREV_INUSE
? ? 最小堆原則 : malloc(0)會分配0x20的空間,prev_size + size + 數(shù)據(jù)對齊的0x10字節(jié)
? ? prev_inuse 漏洞利用

fd / bk
釋放到bins鏈有效
fd指向下一個(非物理相鄰)空閑的chunk
bk指向上一個(非物理相鄰)空閑的chunk
通過fd和bk可以將空閑的chunk塊加入到空閑的chunk鏈表進行統(tǒng)一管理。
fd_nextsize / bk_nextsize
釋放到bins鏈有效,不過其用于較大的chunk(large chunk)
fd_nextsize指向前一個與當前chunk大小不同的第一個空閑塊,不包含bin的頭指針
bk_nextsize指向后一個與當前chunk大小不同的第一個空閑塊,不包含bin的頭指針
一般空閑的largechunk在fd的遍歷順序中,按照從大到小的順序排列,可以避免在尋找合適的chunk時挨個遍歷。
__libc_malloc
void *__libc_malloc (size_t bytes)
? ? {
? ? mstate ar_ptr;
? ? void *victim;
? ? void *(*hook) (size_t, const void *)
? ? = atomic_forced_read (__malloc_hook);
? ? if (__builtin_expect (hook != NULL, 0))
? ? return (*hook)(bytes, RETURN_ADDRESS (0));
? ? #if USE_TCACHE
? ? /* int_free also calls request2size, be careful to not pad twice. */
? ? size_t tbytes;
? ? checked_request2size (bytes, tbytes);? ?//注意:用戶申請的字節(jié)一旦進入申請內存函數(shù)被轉化為了無符號整數(shù)
? ? size_t tc_idx = csize2tidx (tbytes);
? ? MAYBE_INIT_TCACHE ();
? ? DIAG_PUSH_NEEDS_COMMENT;
? ? if (tc_idx < mp_.tcache_bins
? ? /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
? ? && tcache
? ? && tcache->entries[tc_idx] != NULL)
? ? {
? ? return tcache_get (tc_idx);
? ? }
? ? DIAG_POP_NEEDS_COMMENT;
? ? #endif
? ? if (SINGLE_THREAD_P)
? ? {
? ? victim = _int_malloc (&main_arena, bytes);
? ? assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
? ? &main_arena == arena_for_chunk (mem2chunk (victim)));
? ? return victim;
? ? }
? ? arena_get (ar_ptr, bytes);
? ? victim = _int_malloc (ar_ptr, bytes);
? ? /* Retry with another arena only if we were able to find a usable arena
? ? before. */
? ? if (!victim && ar_ptr != NULL)
? ? {
? ? LIBC_PROBE (memory_malloc_retry, 1, bytes);
? ? ar_ptr = arena_get_retry (ar_ptr, bytes);
? ? victim = _int_malloc (ar_ptr, bytes);
? ? }
? ? if (ar_ptr != NULL)
? ? __libc_lock_unlock (ar_ptr->mutex);
? ? assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
? ? ar_ptr == arena_for_chunk (mem2chunk (victim)));
? ? return victim;
? ? }
該函數(shù)會首先檢查是否有內存分配函數(shù)的鉤子函數(shù)(__malloc_hook),這個主要用于用戶自定義的堆分配函數(shù)。
這就造成了一個利用點:將__malloc_hook指針指向的內容改為one_gadget的地址,再次malloc的時候就會直接啟動shell。__malloc_hook -> one_gadget(直接起shell的地址)這時不能將其修改為system的地址,因為system的參數(shù)為字符型,而malloc_hook的參數(shù)為無符號整數(shù)。
接著會尋找一個arena來試圖分配內存,然后調用__int_malloc函數(shù)去申請對應的內存
如果分配失敗的話,ptmalloc會試圖再去尋找一個可用的arena,并分配內存如果申請到了arena,那么在退出之前還得解鎖(__libc_lock_lock)
判斷目前的狀態(tài)是否滿足以下條件
要么沒有申請到內存
要么是mmap的內存
要么申請的的內存必須在其所分配的arena中
assert
最后返回內存,進入__int_malloc
__int_malloc
__int_malloc是內存分配的核心函數(shù),其核心思路為:
它根據(jù)用戶申請的內存塊大小以及相應大小chunk通常使用的頻度,依次實現(xiàn)了不同的分配方法它由小大到大依次檢查不同的bin中是否有相應的空閑塊可以滿足用戶請求的內存當所有空閑的chunk都無法滿足時,他會考慮top_chunk當top_chunk也無法滿足時,堆分配器才會進行內存塊申請
1. 定義變量

2. 判斷有沒有可用的arena
如果沒有可用的arena,則返回系統(tǒng)調用mmap去申請一塊內存

3. 判斷是否在fastbin范圍
如果申請的chunk的大小正好位于fastbin的范圍,則從fastbin的頭節(jié)點開始取chunk。需要注意的是,這里比較的是無符號整數(shù)調用remove_fb取出,并返回得到的fastbin的頭

4. 判斷是否在smallbin
如果獲取的內存塊的范圍為smallbin的范圍,執(zhí)行以下流程找到其大小對應的下標,判斷其鏈表是否為空,不為空則取最后一個

注意,為了防止一個堆塊能夠正常free且不前向后并,需要修改當前堆塊的物理相鄰的緊接著的2個堆塊的inuse位為1。
5.調用consolidate
當fastbin,small bin中的chunk都不能滿足要求時,就會考慮是不是largebin,在此之前先調用malloc_consolidate處理fastbin中的chunk

將有可能合并的chunk先進行合并后放到unsorted bin中,不能合并的就直接放到unsorted bin中,然后再進入大循環(huán),以減少堆中的碎片。只有在分配一個size在largebin范圍內的堆塊,才能觸發(fā)malloc_consolidate
6. 小總結
在fastbin范圍內,先判斷對應鏈表是否為空,不為空則取剛放入的chunk在smallbin范圍內,先判斷對應鏈表是否為空,不為空則取第一個放入的chunk這兩者都無法匹配用戶申請的chunk時,就會進入大循環(huán)
7. 進入大循環(huán)
a. 嘗試從unsorted bin中分配用戶需要的內存b. 嘗試從large bin中分配用戶需要的內存b. 嘗試從top_chunk中分配用戶需要的內存
8. 從unsorted bin中分配nb
如果申請的size小于unsorted bin中符合要求的chunk的size,會對其進行切割,剩下的進入last_remainder(由unsorted bin管理)如果unsorted bin中沒有滿足要求的chunk時,會先place in order整理,然后再去large bin中尋找
9. 從large bin中分配nb
注意,large bin中的堆塊不會split,不滿足的話就從top_chunk中切割
10. 大循環(huán)之對于unsorted bin的check
對于size的check:檢查當前size是否滿足對齊的 要求對于fd和bk的check:bck -> fd != victim對于double free的check:next->prev_inuse = 0
11. 大循環(huán)之切割unsorted bin
如果用戶請求為small bin chunk,那么我們首先考慮last_remainder如果last_remainder分割后還夠可以作為一個chunk,則使用set_head,set_foot設置標志位,將last_remainder放入原來unsorted bin的位置

12. 大循環(huán)之取出unsorted bin
首先將unsorted bin取出,如果其size和我們的nb(need bytes)一樣則直接放回這個unsorted bin
13. 大循環(huán)之放入對應的bin
根據(jù)取出的size來判斷應該放入哪個bin,放入small bin的時候則雙向鏈表插入在else if中處理large bin的邏輯,包括大小排序以及fd_nextsize和bk_nextsize

14. 大循環(huán)總結
整個過程迭代了10000次

__int_malloc的大循環(huán)主要用來處理unsorted bin如果整個循環(huán)沒有找到合適的bin,說明所有的unsorted bin的大小都不滿足要求如果經過了10000次的循環(huán),所有的unsorted bin中的bin都被放入了對應的bin中,即small bin放入對應的index中,large bin排好序后放入對應的index中
15. 大循環(huán)之large bin
如果請求的chunk在large bin范圍內,就在對應的bin中從小到大依次掃描,直到找到第一個合適的,并不一定精確

切割后的remainder會被放入到unsorted bin中,同時設置標志位等信息

16. 尋找較大的chunk

如果走到了這里,說明對于用戶所需的chunk,不能直接從其對應的合適的bin中獲取chunk,需要掃描所有的bin,查找比當前bin更大的fast bin或small bin 以及l(fā)arge bin
17. 找到一個合適的map

18. 取出chunk
切割之后還是一樣,放入到unsorted bin

19. 使用top_chunk
如果所有的bin中的chunk都沒有辦法直接滿足要求(即不合并),或者沒有空閑的chunk時,就只能使用top_chunk了

20. top_chunk不夠用
? > 如果top_chunk不夠用的時候并不是直接申請內存,而是先調用consolidate合并空閑的fastbin
? ? >
? ? > 然后等待下次循環(huán)再去判斷是否夠用,不夠用才會調用sysmalloc申請內存
? ? >
? ? > 調用再次申請內存
