1 概述
在 glibc-2.3.x. 之後,glibc 中內建了ptmalloc2。
可以下載下傳glibc源碼檢視ptmalloc
http://ftp.gnu.org/gnu/glibc/
檢視glibc版本
[email protected]:~/tmp$ ldd --version ldd (Ubuntu GLIBC 2.23-0ubuntu9) 2.23 |
這裡主要參考:
https://ctf-wiki.github.io/ctf-wiki/pwn/heap
本文參考的glibc源碼是glibc-2.25.tar.xz
了解ptmalloc堆最後的辦法是檢視相關資料後看源碼。word文檔拷貝的時候顔色丢失了,格式也有丢失。先這樣。
2 API
2.1 其它
2.1.1 catomic_compare_and_exchange_val_acq
如果*MEM等于OLDVAL,則将*MEM存儲為NEWVAL,傳回OLDVAL;
#ifndef catomic_compare_and_exchange_val_acq
# ifdef __arch_c_compare_and_exchange_val_32_acq
# define catomic_compare_and_exchange_val_acq(mem, newval, oldval) \
__atomic_val_bysize (__arch_c_compare_and_exchange_val,acq, \
mem, newval, oldval)
# else
# define catomic_compare_and_exchange_val_acq(mem, newval, oldval) \
atomic_compare_and_exchange_val_acq (mem, newval, oldval)
# endif
#endif
2.1.2 catomic_compare_and_exchange_val_rel
如果*MEM等于OLDVAL,則将*MEM存儲為NEWVAL,傳回OLDVAL;
#ifndef catomic_compare_and_exchange_val_rel
# ifndef atomic_compare_and_exchange_val_rel
# define catomic_compare_and_exchange_val_rel(mem, newval, oldval) \
catomic_compare_and_exchange_val_acq (mem, newval, oldval)
# else
# define catomic_compare_and_exchange_val_rel(mem, newval, oldval) \
atomic_compare_and_exchange_val_rel (mem, newval, oldval)
# endif
#endif
2.1.3 Perturb_byte
l perturb_byte:一個位元組,用于初始化配置設定的記憶體,出于調試的目的。
l __libc_mallopt(int param_number, int value):設定perturb_byte
l alloc_perturb (char *p, size_t n):記憶體初始化為perturb_byte^0xff
l free_perturb (char *p, size_t n):記憶體初始化為perturb_byte
l do_set_perturb_byte (int32_t value):設定perturb_byte
static int perturb_byte;
static void
alloc_perturb (char *p, size_t n)
{
if (__glibc_unlikely (perturb_byte))
memset (p, perturb_byte ^ 0xff, n);
}
static void
free_perturb (char *p, size_t n)
{
if (__glibc_unlikely (perturb_byte))
memset (p, perturb_byte, n);
}
static inline int
__always_inline
do_set_perturb_byte (int32_t value)
{
LIBC_PROBE (memory_mallopt_perturb, 2, value, perturb_byte);
perturb_byte = value;
return 1;
}
int
__libc_mallopt (int param_number, int value){
//......
switch (param_number)
{
case M_PERTURB:
do_set_perturb_byte (value);
break;
}
//......
}
2.2 malloc_init_state
l 初始化arena header(malloc_state)結構:
初始化bins數組,構造bin雙連結清單;
設定NONCONTIGUOUS_BIT标記;
設定fastbin使用者資料的最大值:global_max_fast;
設定FASTCHUNKS_BIT标記;
初始化top chunk;
static void
malloc_init_state (mstate av)
{
int i;
mbinptr bin;
for (i = 1; i < NBINS; ++i)
{
bin = bin_at (av, i);
bin->fd = bin->bk = bin;
}
#if MORECORE_CONTIGUOUS
if (av != &main_arena)
#endif
set_noncontiguous (av);
if (av == &main_arena)
set_max_fast (DEFAULT_MXFAST);
av->flags |= FASTCHUNKS_BIT;
av->top = initial_top (av);
}
2.3 Unlink
unlink 用來将一個雙向 bin 連結清單中的一個 chunk 取出來
參數:
AV:arena header(malloc_state)
P :将要unlink的chunk
BK:P後面的chunk <--
FD:P前面的chunk -->
3個chunk的順序
-------->位址增加
BK P FD
具體過程如下:
将chunk從FD/BK連結清單中摘除;
如果是large chunk,則将chunk從fd_nextsize/bk_nextsize連結清單中摘除
#define unlink(AV, P, BK, FD) { \ FD = P->fd; \ BK = P->bk; \ // 檢查3個chunk的BK/FD連結是否一緻 if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ else { \ // unlink P FD->bk = BK; \ BK->fd = FD; \ // 如果P的大小不屬于small bin if (!in_smallbin_range (chunksize_nomask (P)) \ // P的fd_nextsize字段非空,即位于large bin中 // 這裡期望fd_nextsize字段為NULL,即大機率為unsorted bin,小機率為large bin && __builtin_expect (P->fd_nextsize != NULL, 0)) { \ // 檢查3個chunk的fd_nextsize/bk_nextsize連結是否一緻 if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \ || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \ malloc_printerr (check_action, \ "corrupted double-linked list (not small)", \ P, AV); \ // 此時P在fd_nextsize連結清單中 // 如果FD不在fd_nextsize連結清單中,則将FD加傳入連結表中 if (FD->fd_nextsize == NULL) { \ // 如果P連結到自身,則令FD也連結到自身 if (P->fd_nextsize == P) \ FD->fd_nextsize = FD->bk_nextsize = FD; \ else { \ // 否則我們需要将 FD 插入到 nextsize 形成的雙連結清單中 // 更新FD的fd_nextsize/bk_nextsize // 更新P->fd_nextsize/P->bk_nextsize 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都在fd_nextsize連結清單中,将P從fd_nextsize連結清單中摘除 P->fd_nextsize->bk_nextsize = P->bk_nextsize; \ P->bk_nextsize->fd_nextsize = P->fd_nextsize; \ } \ } \ } \ } |
1. __builtin_expect
l 函數__builtin_expect()是GCC v2.96版本引入的, 其聲明如下:
long __builtin_expect(long exp, long c);
參數
exp 為一個整型表達式, 例如: (ptr != NULL)
c 必須是一個編譯期常量, 不能使用變量
傳回值
等于 第一個參數 exp
功能
GCC 提供了這個内建函數來幫助程式員處理分支預測.
允許程式員将最有可能執行的分支告訴編譯,即exp的值很可能是c;
GCC在編譯過程中,會将可能性更大的代碼緊跟着前面的代碼,進而減少指令跳轉帶來的性能上的下降, 達到優化程式的目的。(此時CPU流水線預取指令會起作用)
2.4 Malloc
見注釋,最終調用的是_int_malloc
strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, 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)); //接着會尋找一個 arena 來試圖配置設定記憶體 arena_get (ar_ptr, bytes); //調用 _int_malloc 函數去申請對應的記憶體 victim = _int_malloc (ar_ptr, bytes); //嘗試再去尋找一個可用的 arena,并配置設定記憶體 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); } //unlock if (ar_ptr != NULL) __libc_lock_unlock (ar_ptr->mutex); //判斷目前的狀态是否滿足以下條件 //要麼沒有申請到記憶體 //要麼是 mmap 的記憶體 //要麼申請到的記憶體必須在其所配置設定的arena中 assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim))); return victim; } libc_hidden_def (__libc_malloc) |
2.5 _int_malloc
static void *
_int_malloc (mstate av, size_t bytes)
_int_malloc 是記憶體配置設定的核心函數,其核心思路有如下
1. Fast bin服務請求
2. Small bin服務請求
3. 如果是large chunk,則合并fast bin
4. 循環處理
1) Unsorted bin服務請求
(周遊的unsorted chunk要麼比對使用者請求被傳回,要麼放入對應的bin中)
2) Large bin服務請求
3) next largest bin服務請求
4) top chunk服務請求
5) 合并fast bin,下次嘗試
6) 從系統請求記憶體
具體過程如下:
1. 沒有可用的arena,則使用sysmalloc從mmap擷取chunk;
2. chunk大小屬于fast bin,嘗試從fast bin(單連結清單)連結清單頭部擷取;
3. chunk大小屬于small bin,嘗試從small bin(環形雙連結清單)連結清單尾部擷取;
4. chunk大小屬于large bin,先合并 fast bin以解決分片問題;
5. 循環處理
l 這裡使用外循環,是因為我們可能在進行中合并了一個滿足要求的chunk,需要重新嘗試。最多重新嘗試一次,否則我們将擴充記憶體來服務請求。
l unsorted bin服務請求
Ø last remainder chunk服務請求
(使用者請求為 small chunk;
last remainder chunk是 unsorted bin 中的唯一的chunk;
Last remainder大小大于使用者請求的大小;)
Ø 嚴格比對(exact fit)服務請求
Ø 如果是small chunk,則插入到small bin連結清單的開頭
Ø 如果為large chunk,則按大小降序放入large bin(大小相同時總是插入第二個位置);同時構造fd_nextsize/bk_nextsize連結清單;
l Large bin服務large chunk請求
如果找到則切分後将剩餘的部分放入unsorted bin
l next largest bin服務請求
在下一個非空bin(bin數組中下一個索引)中擷取chunk,切分後返還給使用者
l top chunk服務請求
l 合并fast bin,下次嘗試
l 從系統請求記憶體
2.5.1 convert
将使用者申請的記憶體大小轉換為内部的chunk大小
checked_request2size (bytes, nb);
2.5.2 arena
如果沒有可用的arena,則使用sysmalloc從mmap擷取chunk
if (__glibc_unlikely (av == NULL))
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
2.5.3 Fastbin
l 如果申請的 chunk 的大小位于 fast bin 範圍内,則從對應的fast bin連結清單的頭部中擷取并傳回free chunk;(fast bin是單連結清單)
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp = *fb;
// 擷取fast bin連結清單中的第一個元素,同時更新fastbinsY中的fast bin頭指針
// 如果fast bin連結清單是空的,則退出循環
do
{
victim = pp;
if (victim == NULL)
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);
if (victim != 0)
{
// 檢查取到的 chunk 大小是否與相應的 fastbin 索引一緻。
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
// 細緻的檢查。。隻有在 DEBUG 的時候有用
check_remalloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
// 如果設定了perturb_type, 則将擷取到的chunk初始化為 perturb_type ^ 0xff
alloc_perturb (p, bytes);
return p;
}
}
2.5.4 Small bin
l 如果申請的 chunk 的大小位于 small bin 範圍内(small bin是環形循環雙連結清單),則從連結清單中取出small bin的最後一個chunk傳回給使用者;
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
// 先執行 victim = last(bin),擷取 small bin 的最後一個 chunk
// 如果 victim = bin ,那說明該 bin 為空。
// 如果不相等,那麼會有兩種情況
if ((victim = last (bin)) != bin)
{
// 第一種情況,small bin 還沒有初始化。
if (victim == 0)
malloc_consolidate (av);
// 第二種情況,small bin 中存在空閑的 chunk
else
{
// 擷取 small bin 中倒數第二個 chunk
bck = victim->bk;
// 擷取 small bin 中倒數第二個 chunk
if (__glibc_unlikely (bck->fd != victim))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
// 修改 small bin 連結清單,将 small bin 的最後一個 chunk 取出來
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}
2.5.5 malloc_consolidate
如果是large chunk,繼續之前,先合并 fastbins。這可以解決一般由fastbins帶來的分片問題。實際上,程式要麼請求small chunk,要麼請求large chunk,很少會混合請求。是以合并在大多數程式中不會被調用。
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}
2.5.6 大循環
l unsorted bin服務請求
Ø last remainder chunk服務請求
(使用者請求為 small chunk;
last remainder chunk是 unsorted bin 中的唯一的chunk;
Last remainder大小大于使用者請求的大小;)
Ø 嚴格比對(exact fit)服務請求
Ø 如果是small chunk,則插入到small bin連結清單的開頭
Ø 如果為large chunk,則按大小降序放入large bin(大小相同時總是插入第二個位置);同時構造fd_nextsize/bk_nextsize連結清單;
l Large bin服務large chunk請求
如果找到則切分後将剩餘的部分放入unsorted bin
l next largest bin服務請求
在下一個非空bin(bin數組中下一個索引)中擷取chunk,切分後返還給使用者
l top chunk服務請求
l fast bin服務請求
l 從系統請求記憶體
1. 這裡使用外循環,是因為我們可能在進行中合并了一個滿足要求的chunk,需要重新嘗試。最多重新嘗試一次,否則我們将擴充記憶體來服務請求。
for (;; )
{
int iters = 0;
//......
}
2.5.7 大循環-unsorted bin周遊
使用bk周遊unsorted bin
2.5.7.1 擷取一個unsorted chunk
// 如果 unsorted bin 不為空
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
// victim 為 unsorted bin 的最後一個 chunk
// bck 為 unsorted bin 的倒數第二個 chunk
bck = victim->bk;
if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize_nomask (victim)
> av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
// 得到victim對應的chunk大小
size = chunksize (victim);
2.5.7.2 last remainder chunk中擷取
如果滿足以下條件,則使用last remainder chunk服務請求。這會有助于使得連續的small chunk的請求得到的位置相鄰。
1) 使用者請求為 small chunk
2) last remainder chunk是 unsorted bin 中的唯一的chunk
3) Last remainder大小大于使用者請求的大小
//滿足3個條件
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
//從last remainder chunk中取出使用者請求的chunk,更新unsorted bin連結清單和标志
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
2.5.7.3 将Unsorted chunk從連結清單中移除
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
移除後要麼被使用,要麼傳回給使用者(exact fit),要麼放入相關的bin中;
2.5.7.4 精确比對
如果取出的unsorted chunk精确比對,則設定标志後,傳回給使用者;
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
2.5.7.5 unsorted chunk放入small bin
如果是small chunk,則插入到small bin連結清單的開頭
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else ......
// 放到對應的 bin 中,插入victim,構成 bck<-->victim<-->fwd
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
2.5.7.6 unsorted chunk放入large bin
如果為large chunk,則按大小降序放入large bin(大小相同時總是插入第二個位置);同時構造fd_nextsize/bk_nextsize連結清單;
else
{
// large bin範圍,bck為頭結點,fwd為第一個結點
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
// bin為非空連結清單
if (fwd != bck)
{
// 加速比較,連結清單裡的chunk都會設定該位
size |= PREV_INUSE;
//為什麼最後一個chunk屬于main arena呢?
assert (chunk_main_arena (bck->bk));
//周遊的chunk比目前最小的還要小
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))
{
//bck為最後一個結點,fw為頭結點
fwd = bck;
bck = bck->bk;
//victim插入fd_nextsize/bk_nextsize連結清單
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{//周遊的chunk比目前最小的要大于等于
//為什麼第一個chunk屬于main arena呢?
assert (chunk_main_arena (fwd));
// 從連結清單頭部開始找到大小小于等于 victim 的 chunk
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
//為什麼每個chunk都屬于main arena呢?
assert (chunk_main_arena (fwd));
}
// 如果大小一樣,插入第二個位置,nextsize指針沒有改變,不需要修改
if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd))
fwd = fwd->fd;
else
{// 如果大小不一樣,則插入fd_nextsize/bk_nextsize雙連結清單
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
else//空連結清單
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
2.5.8 大循環-large bin服務large chunk請求
如果請求的是large chunk,就在large bin 中從小到大進行掃描,找到第一個合适的。如果找到則切分後将剩餘的部分放入unsorted bin;
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);
// 如果對應的 bin 為空或者其中最大的chunk也小于使用者的請求,那就跳過
if ((victim = first (bin)) != bin && (unsigned long) chunksize_nomask (victim) >= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
// 反向周遊連結清單,直到找到第一個不小于使用者請求大小的chunk
while (((unsigned long) (size = chunksize (victim)) < (unsigned long) (nb)))
victim = victim->bk_nextsize;
// 如果最終取到的chunk不是該bin中的最後一個chunk,并且該chunk與其前面的chunk(fd)
// 的大小相同,那麼我們就取其前面的chunk,這樣可以避免調整bk_nextsize,fd_nextsize
// 連結清單。因為大小相同的chunk隻有一個會被串在nextsize鍊上
if (victim != last (bin) && chunksize_nomask (victim) == chunksize_nomask (victim->fd))
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd);
// 剩下的大小不足以當做一個塊,設定标志,不切分
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
else // 剩下的大小還可以作為一個chunk,進行分割,剩餘部分放入unsorted bin
{
remainder = chunk_at_offset (victim, nb);
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
2.5.9 大循環-尋找next largest bin
如果走到了這裡,那說明對于使用者所需的chunk,不能直接從其對應的bin中擷取chunk,此時我們在下一個非空bin(bin數組中下一個索引)中擷取chunk,切分後返還給使用者。
【準備工作】
// 從下一個更大的bin開始掃描最小比對的chunk。
// 利用bitmap可以避免掃描空的bin。
// 擷取下一個更大的bin
++idx;
bin = bin_at (av, idx);
//擷取對應的bitmap标記
block = idx2block (idx);
map = av->binmap[block];
bit = idx2bit (idx);
for (;; )
{
//......
}
【找到合适的map--跳過位沒有設定的block】
//bit > map: bit中隻有一個1,這說明map中這個1對應的高位均為0,
//為0表示對應的bin為空,則此block可以跳過
//bit <= map && bit == 0: bit = idx2bit (idx);
//bit初始化時不可能為0(有一個位置1),但是後面有左移位操作,如果剛好移到了最高位,則會為0
//bit為0時會進入下一個block,bit重新初始化為1
if (bit > map || bit == 0)
{
do
{
//跳過block
if (++block >= BINMAPSIZE)
goto use_top;
}
while ((map = av->binmap[block]) == 0);//map為0表示此block所有的位均為0,跳過
bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}
【找到合适的bin】
//找到一個非空的bin
while ((bit & map) == 0)
{
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}
【檢查bin是否為空】
為空時清空binMap中對應的标志位
victim = last (bin);
if (victim == bin)
{
av->binmap[block] = map &= ~bit;
bin = next_bin (bin);
bit <<= 1;
}
【unlink-切分-擷取chunk】
這部分的代碼和一般的切分代碼流程差不多,在“大循環-large bin服務large chunk請求”中也可以看到;
else
{
size = chunksize (victim);
assert ((unsigned long) (size) >= (unsigned long) (nb));
remainder_size = size - nb;
unlink (av, victim, bck, fwd);
// 剩下的大小不足以當做一個塊,設定标志,不切分
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
else // 剩下的大小還可以作為一個chunk,進行分割,剩餘部分放入unsorted bin
{
remainder = chunk_at_offset (victim, nb);
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks 2";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE | av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
2.5.10 大循環-使用top chunk
如果所有的 bin 中的 chunk 都沒有辦法直接滿足要求(即不合并),或者說都沒有空閑的 chunk。那麼我們就隻能使用 top chunk 了。
l Top chunk服務請求
l 合并fast bin,下次嘗試
l 從系統請求記憶體
use_top:
victim = av->top;
size = chunksize (victim);
// 如果分割之後,top chunk 大小仍然滿足 chunk 的最小大小,那麼就可以直接進行分割
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
// 否則,判斷是否有 fast chunk
else if (have_fastchunks (av))
{
// 先執行一次fast bin的合并
malloc_consolidate (av);
// 判斷需要的chunk是在small bin範圍内還是large bin範圍内
// 并計算對應的索引
// 等待下次再看看是否可以
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}
// 否則的話,我們就隻能從系統中再次申請一點記憶體了
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
2.6 Free
見注釋,最終調用的是_int_free
// 除非使用mallopt關閉,釋放非常大的空間會自動觸發将不使用的記憶體歸還給系統
void __libc_free(void*);
libc_hidden_proto (__libc_free)
strong_alias (__libc_free, __cfree) weak_alias (__libc_free, cfree)
strong_alias (__libc_free, __free) strong_alias (__libc_free, free)
void
__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p;
void (*hook) (void *, const void *) = atomic_forced_read (__free_hook);
// 判斷是否有鈎子函數 __free_hook
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0));
return;
}
if (mem == 0)
return;
p = mem2chunk (mem);
// 如果該塊記憶體是mmap得到的
if (chunk_is_mmapped (p))
{
if (!mp_.no_dyn_threshold
&& chunksize_nomask (p) > mp_.mmap_threshold
&& chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX
&& !DUMPED_MAIN_ARENA_CHUNK (p))
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk (p);
return;
}
ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
}
2.7 _int_free
static void
_int_free (mstate av, mchunkptr p, int have_lock)
功能:釋放chunk,放入fast bin或合并後放入unsorted bin或合并到top chunk
具體過程如下:
如果屬于fast bin,則插入fast bin連結清單頭部;
否則如果不是mmap的記憶體,則進行合并;
後向合并-合并低位址 CHUNK
下一塊不是top chunk-前向合并-合并高位址chunk
合并後把 chunk 放在 unsorted chunk 連結清單的頭部
下一塊是top chunk-合并到top chunk
如果合并後chunk大小大于64KB,則合并fast chunk,收縮top;
否則如果是mmap的記憶體,則釋放mmap的chunk
2.7.1 簡單的檢查
檢查大小與對齊
size = chunksize (p);
//如果p+size繞過了位址空間的結尾,或者p沒有對齊,則出錯了
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
{
errstr = "free(): invalid pointer";
errout:
if (!have_lock && locked)
__libc_lock_unlock (av->mutex);
malloc_printerr (check_action, errstr, chunk2mem (p), av);
return;
}
// 大小沒有最小的chunk大,或者說,大小不是MALLOC_ALIGNMENT的整數倍,則有問題
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
{
errstr = "free(): invalid size";
goto errout;
}
// 檢查該chunk是否處于使用狀态,非調試狀态下沒有作用
check_inuse_chunk(av, p);
2.7.2 Fast bin
如果屬于fast bin,則插入fast bin連結清單頭部;
//大小屬于fast bin
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
#if TRIM_FASTBINS
//預設 #define TRIM_FASTBINS 0,是以預設情況下下面的語句不會執行
//如果目前chunk是fast chunk,并且下一個chunk是top chunk,則不能插入
//此時由于定義了TRIM_FASTBINS,這個chunk會和top chunk合并
&& (chunk_at_offset(p, size) != av->top)
#endif
)
{
//檢查下一個chunk的大小上限和下限
// 下一個chunk的大小不能小于兩倍的SIZE_SZ,并且
// 下一個chunk的大小不能大于system_mem, 一般為132k
// 如果出現這樣的情況,就報錯
if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))
<= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{
if (have_lock
|| ({ assert (locked == 0);
__libc_lock_lock (av->mutex);
locked = 1;
chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem;
}))
{
errstr = "free(): invalid next size (fast)";
goto errout;
}
if (! have_lock)
{
__libc_lock_unlock (av->mutex);
locked = 0;
}
}
// 将chunk的mem部分全部設定為perturb_byte
free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);
// 設定fast chunk的标記位
set_fastchunks(av);
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
//将P插入fast bin連結清單頭部
mchunkptr old = *fb, old2;
unsigned int old_idx = ~0u;
do
{
// 防止對 fast bin double free
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
if (have_lock && old != NULL)
old_idx = fastbin_index(chunksize(old));
//P插入fast bin連結清單頭部
p->fd = old2 = old;
}
while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2);
if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}
}
2.7.3 合并非 mmap 的空閑 chunk
合并其他非mmap的chunks。
else if (!chunk_is_mmapped(p)) {
if (! have_lock) {
__libc_lock_lock (av->mutex);
locked = 1;
}
nextchunk = chunk_at_offset(p, size);
檢查是否有異常情況
// 目前free的chunk不能是top chunk
if (__glibc_unlikely (p == av->top))
{
errstr = "double free or corruption (top)";
goto errout;
}
// 目前free的chunk的下一個chunk不能超過arena的邊界
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
{
errstr = "double free or corruption (out)";
goto errout;
}
// 目前要free的chunk的使用标記沒有被标記,double free
if (__glibc_unlikely (!prev_inuse(nextchunk)))
{
errstr = "double free or corruption (!prev)";
goto errout;
}
//下一個chunk的大小是否超出範圍
nextsize = chunksize(nextchunk);
if (__builtin_expect (chunksize_nomask (nextchunk) <= 2 * SIZE_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0))
{
errstr = "free(): invalid next size (normal)";
goto errout;
}
填充
free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);
後向合并-合并低位址 CHUNK
//如果低位址chunk空閑,則合并後unlink
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
下一塊不是top chunk-前向合并-合并高位址chunk
合并後把 chunk 放在 unsorted chunk 連結清單的頭部
if (nextchunk != av->top) {
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
// 如果不在使用,合并,否則清空目前chunk的使用狀态
if (!nextinuse) {
unlink(av, nextchunk, bck, fwd);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);
// 把 chunk 放在 unsorted chunk 連結清單的頭部
bck = unsorted_chunks(av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "free(): corrupted unsorted chunks";
goto errout;
}
p->fd = fwd;
p->bk = bck;
if (!in_smallbin_range(size))
{
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
bck->fd = p;
fwd->bk = p;
set_head(p, size | PREV_INUSE);
set_foot(p, size);
check_free_chunk(av, p);
}
下一塊是top chunk-合并到top chunk
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
check_chunk(av, p);
}
向系統返還記憶體
如果合并後chunk大小大于64KB,則合并fast chunk,收縮top;
//如果釋放大的空間,會合并周邊的chunks。
//然後,如果top超過了trim的門檻值,則使用malloc_trim來減少top
//在此之前,我們要先合并fast bin
//我們并不想每一次free都執行合并,作為一種折中,
//如果到達FASTBIN_CONSOLIDATION_THRESHOLD(64KB)才合并fast bin
//合并才執行top的收縮
if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
if (have_fastchunks(av))
malloc_consolidate(av);
if (av == &main_arena) {
#ifndef MORECORE_CANNOT_TRIM
if ((unsigned long)(chunksize(av->top)) >=(unsigned long)(mp_.trim_threshold))
systrim(mp_.top_pad, av);
#endif
} else {
heap_info *heap = heap_for_ptr(top(av));
assert(heap->ar_ptr == av);
heap_trim(heap, mp_.top_pad);
}
}
if (! have_lock) {
assert (locked);
__libc_lock_unlock (av->mutex);
}
2.7.4 釋放mmap的chunk
else {
munmap_chunk (p);
}
1.8 malloc_consolidate
功能:清除fast chunk,能合并則合并,放入unsorted bin或并入top chunk。
具體過程如下:
如果fast bin已經初始化
周遊fast bin
周遊fast chunk
如果低位址chunk空閑,合并之,unlink
如果高位址不是top chunk
如果chunk空閑,合并之,unlink
Fast chunk放入unsorted bin的頭部
如果高位址是top chunk
合并到 top中
如果fast bin沒有初始化
調用malloc_init_state初始化malloc_state
//合并fast bin或初始化malloc_state
static void malloc_consolidate(mstate av)
合并fast chunk
// fast bin已經初始化
if (get_max_fast () != 0) {
// 清除fastbin 标記
clear_fastchunks(av);
unsorted_bin = unsorted_chunks(av);
//移除并合并fast bin中的每個chunk,将其放入unsorted bin
maxfb = &fastbin (av, NFASTBINS - 1); //最大的索引:9,實際上周遊的是fast bin 0~8
fb = &fastbin (av, 0);
do {
p = atomic_exchange_acq (fb, NULL);
if (p != 0) {
do {//周遊某個fast bin中的fast chunk
check_inuse_chunk(av, p);
nextp = p->fd;
//free()中的合并代碼的簡單流線型版本
size = chunksize (p);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
//如果低位址chunk空閑,則合并後unlink,後面會放入unsorted chunk
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
//下一塊不是top chunk-前向合并-合并高位址chunk
//合并後把 chunk 放在 unsorted chunk 連結清單的頭部
if (nextchunk != av->top) {
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
if (!nextinuse) {
size += nextsize;
unlink(av, nextchunk, bck, fwd);
} else
clear_inuse_bit_at_offset(nextchunk, 0);
first_unsorted = unsorted_bin->fd;
unsorted_bin->fd = p;
first_unsorted->bk = p;
if (!in_smallbin_range (size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
else {//下一塊是top chunk-合并到top chunk
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
} while ( (p = nextp) != 0);
}
} while (fb++ != maxfb);
}
初始化malloc_state
else {
malloc_init_state(av);
check_malloc_state(av);
}
3 結論
【主要的API】
1. malloc_consolidate
功能:
清空fast bins,fast chunk能合并則合并,放入unsorted bin或并入top chunk。
具體過程如下:
如果fast bin已經初始化
周遊fast bin
周遊fast chunk
如果低位址chunk空閑,合并之,unlink
如果高位址不是top chunk
如果chunk空閑,合并之,unlink
Fast chunk放入unsorted bin的頭部
如果高位址是top chunk
合并到 top中
如果fast bin沒有初始化
調用malloc_init_state初始化malloc_state
2. Unlink
unlink 用來将一個雙向 bin 連結清單中的一個 chunk 取出來
參數:
AV:arena header(malloc_state)
P :将要unlink的chunk
BK:P後面的chunk <--
FD:P前面的chunk -->
3個chunk的順序
-------->位址增加
BK P FD
具體過程如下:
将chunk從FD/BK連結清單中摘除;
如果是large chunk,則将chunk從fd_nextsize/bk_nextsize連結清單中摘除
3. _int_malloc
1.Fast bin服務請求
2.Small bin服務請求
3.如果是large chunk,則合并fast bin
4.循環處理
1) Unsorted bin服務請求
(周遊的unsorted chunk要麼比對使用者請求被傳回,要麼放入對應的bin中)
2) Large bin服務請求
3) next largest bin服務請求
4) top chunk服務請求
5) 合并fast bin,下次嘗試
6) 從系統請求記憶體
4. _int_free
功能:釋放chunk,放入fast bin或合并後放入unsorted bin或合并到top chunk
具體過程如下:
如果屬于fast bin,則插入fast bin連結清單頭部;
否則如果不是mmap的記憶體,則進行合并;
後向合并-合并低位址 CHUNK
下一塊不是top chunk-前向合并-合并高位址chunk
合并後把 chunk 放在 unsorted chunk 連結清單的頭部
下一塊是top chunk-合并到top chunk
如果合并後chunk大小大于64KB,則合并fast chunk,收縮top;
否則如果是mmap的記憶體,則釋放mmap的chunk
3 參考文檔
1. https://ctf-wiki.github.io/ctf-wiki/pwn/heap/heap_structure/
2. https://ctf-wiki.github.io/ctf-wiki/pwn/heap/heap_implementation_details/