前言
對一些有趣的堆相關的漏洞的利用做一個記錄,如有差錯,請見諒。
文中未做說明 均是指 glibc 2.23
相關引用已在文中進行了標注,如有遺漏,請提醒。
簡單源碼分析
本節只是簡單跟讀了一下 malloc
和 free
的源碼, 說的比較簡單,很多細節還是要自己拿一份源代碼來讀。
堆中的一些數據結構
堆管理結構
struct malloc_state {
mutex_t mutex; /* Serialize access. */
int flags; /* Flags (formerly in max_fast). */
#if THREAD_STATS
/* Statistics for locking. Only used if THREAD_STATS is defined. */
long stat_lock_direct, stat_lock_loop, stat_lock_wait;
#endif
mfastbinptr fastbins[NFASTBINS]; /* Fastbins */
mchunkptr top;
mchunkptr last_remainder;
mchunkptr bins[NBINS * 2];
unsigned int binmap[BINMAPSIZE]; /* Bitmap of bins */
struct malloc_state *next; /* Linked list */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
malloc_state
結構是我們最常用的結構,其中的重要字段如下:fastbins
:存儲多個鏈表。每個鏈表由空閑的fastbin
組成,是fastbin freelist
。top
:top chunk
,指向的是arena
中剩下的空間。如果各種freelist
都為空,則從top chunk
開始分配堆塊。bins
:存儲多個雙向鏈表。意義上和堆塊頭部的雙向鏈表一樣,並和其組成了一個雙向環狀空閑列表(freelist)。這里的bins位於freelist的結構上的頭部,后向指針(bk)指向freelist邏輯上的第一個節點。分配chunk
時從邏輯上的第一個節點分配尋找合適大小的堆塊。
堆塊結構
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
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;
};
prev_size
:相鄰的前一個堆塊大小。這個字段只有在前一個堆塊(且該堆塊為normal chunk
)處於釋放狀態時才有意義。這個字段最重要(甚至是唯一)的作用就是用於堆塊釋放時快速和相鄰的前一個空閑堆塊融合。該字段不計入當前堆塊的大小計算。在前一個堆塊不處於空閑狀態時,數據為前一個堆塊中用戶寫入的數據。libc這么做的原因主要是可以節約4個字節的內存空間,但為了這點空間效率導致了很多安全問題。size
:本堆塊的長度。長度計算方式:size字段長度+用戶申請的長度+對齊。libc以 size_T 長度*2 為粒度對齊。例如 32bit 以 4*2=8byte 對齊,64bit 以 8*2=0×10 對齊。因為最少以8字節對齊,所以size一定是8的倍數,故size字段的最后三位恆為0,libc用這三個bit做標志flag。比較關鍵的是最后一個bit(pre_inuse),用於指示相鄰的前一個堆塊是alloc還是free。如果正在使用,則 bit=1。libc判斷 當前堆塊是否處於free狀態的方法 就是 判斷下一個堆塊的 pre_inuse 是否為 1 。這里也是double free
和null byte offset
等漏洞利用的關鍵。fd &bk
:雙向指針,用於組成一個雙向空閑鏈表。故這兩個字段只有在堆塊free后才有意義。堆塊在alloc狀態時,這兩個字段內容是用戶填充的數據。兩個字段可以造成內存泄漏(libc的bss地址),Dw shoot等效果。- 值得一提的是,堆塊根據大小,libc使用fastbin、chunk等邏輯上的結構代表,但其存儲結構上都是malloc_chunk結構,只是各個字段略有區別,如fastbin相對於chunk,不使用bk這個指針,因為fastbin freelist是個單向鏈表。
Malloc 源碼分析
用戶調用 malloc
時會先進入 __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))// 如果設置了 __malloc_hook 就執行然后返回
return (*hook)(bytes, RETURN_ADDRESS (0));
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
return victim;
}
如果設置了 __malloc_hook
就執行它然后返回, 否則進入 _int_malloc
這個函數就是 malloc
的具體實現
static void *
_int_malloc (mstate av, size_t bytes)
{
/*
計算出實際需要的大小,大小按照 2 * size_t 對齊, 64位: 0x10
所以如個 malloc(0x28) ----> nb = 0x30, 0x10 header + 0x20 當前塊 + 0x8 下一塊的 pre_size
*/
checked_request2size (bytes, nb);
/*
如果是第一次觸發 malloc, 就會調用 sysmalloc---> mmap 分配內存返回
*/
if (__glibc_unlikely (av == NULL))
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
首先把傳入的 bytes
轉換為 chunk
的實際大小,保存到 nb
里面。然后如果是第一次調用 malloc
, 就會進入 sysmalloc
分配內存。
搜索Fastbin
接着會看申請的 nb
是不是在 fastbin
里面,如果是進入 fastbin
的處理流程
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb); // 找到nb 對應的 fastbin 的 索引 idx
mfastbinptr *fb = &fastbin (av, idx);// 找到對應的 fastbin 的指針
mchunkptr pp = *fb;
do
{
victim = pp;
if (victim == NULL)
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);
if (victim != 0) //如果 fastbin 非空,就進入這里
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))// 判斷大小是否滿足 fastbin相應bin的大小要求
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
check_remalloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
首先根據 nb
找到該大小對應的 fastbin
的項, 然后看看該 fastbin
是不是為空,如果非空,就分配該 fastbin
的第一個 chunk
給用戶。
分配過程還會檢查待分配的 chunk
的 size
是不是滿足在該 fastbin
項的限制。
fastbin_index (chunksize (victim)) != idx
搜索Smallbin
如果 fastbin
為空或者 nb
不在 fastbin
里面,就會進入 smallbin
和 largebin
的處理邏輯
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);// 找到 smallbin 索引
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin) // 判斷 bin 中是不是有 chunk
{
if (victim == 0) /* initialization check */
malloc_consolidate (av);
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim)) // 鏈表檢查
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb); //設置下一個chunk的 in_use 位
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}
/*
大內存分配,進入 malloc_consolidate
*/
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}
如果申請的 nb
位於 smallbin
的范圍,就會 fastbin
一樣去找對應的項,然后判斷 bin
是不是為空,如果不空, 分配第一個 chunk
給用戶,分配之前還會校驗該 chunk
是不是正確的。如果為空,就會進入 unsorted bin
的處理了。
__glibc_unlikely (bck->fd != victim)
如果 nb
不滿足 smallbin
,就會觸發 malloc_consolidate
. 然后進入 unsorted bin
搜索Unsorted bin
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 遍歷 unsorted bin
{
bck = victim->bk;
size = chunksize (victim);
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
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;
}
遍歷 unsorted bin
, 如果此時的 unsorted bin
只有一項,且他就是 av->last_remainder
,同時大小滿足
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)
就對當前 unsorted bin
進行切割,然后返回切割后的 unsorted bin
。
否則就先把該 unsorted bin
從 unsorted list
中移除下來,這里用了一個 類似 unlink
的操作,不過沒有檢查 chunk
的指針
/*先摘下該 unsorted bin */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
// 如果申請的大小和該 unsorted bin的大小剛好相等,就直接返回
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
如果申請的大小和該 unsorted bin
的大小剛好相等,就直接返回, 否則就把它放到相應的 bin
里面去。
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
.......
.......
如果 size
在 smallbin
里就放到 smallbin
,否則就放到 large bin
搜索 Largebin
接下來就會去搜索 largebin
了
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size)
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
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;
}
}
使用 Top chunk
victim = av->top;
size = chunksize (victim);
// 如果 top chunk 大小足夠大就從 top 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;
}
/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (have_fastchunks (av))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}
/*
Otherwise, relay to handle system-dependent cases
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
如果 top chunk
的大小足夠就直接切割分配,否則如果此時還有 fastbin
就觸發 malloc_consolidate
重復上述流程,如果沒有 fastbin
調用 sysmalloc
分配內存
Free 源碼分析
_GI___libc_free
首先是 _GI___libc_free
void __fastcall _GI___libc_free(void *ptr)
{
if ( _free_hook )
{
_free_hook(ptr, retaddr);
}
else if ( ptr )
{
v1 = (unsigned __int64)ptr - 16;
v2 = *((_QWORD *)ptr - 1);
if ( v2 & 2 ) // 判斷size位,判斷是不是 mmap 獲得的 chunk
{
if ( !mp_.no_dyn_threshold
&& v2 > mp_.mmap_threshold
&& v2 <= 0x2000000
&& (v1 < (unsigned __int64)dumped_main_arena_start || v1 >= (unsigned __int64)dumped_main_arena_end) )
{
mp_.mmap_threshold = v2 & 0xFFFFFFFFFFFFFFF8LL;
mp_.trim_threshold = 2 * (v2 & 0xFFFFFFFFFFFFFFF8LL);
}
munmap_chunk((mchunkptr)((char *)ptr - 16));
}
else
{
av = &main_arena;
if ( v2 & 4 )
av = *(malloc_state **)(v1 & 0xFFFFFFFFFC000000LL);
int_free(av, (mchunkptr)v1, 0);
}
}
}
如果存在 free_hook
, 就會直接調用 free_hook(ptr)
然后返回。否則判斷被 free
的 內存是否是 mmap
獲取的 ,如果是則使用 munmap_chunk
回收內存,否則進入 _int_free
_int_free
首先會做一些簡單的檢查
size = chunksize (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)
(void) mutex_unlock (&av->mutex);
malloc_printerr (check_action, errstr, chunk2mem (p), av);
return;
}
// 檢查 size 是否 >= MINSIZE ,且是否對齊
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
{
errstr = "free(): invalid size";
goto errout;
}
// 檢查 chunk 是否處於 inuse 狀態
check_inuse_chunk(av, p);
檢查
- 指針是否對齊
- 塊的大小是否對齊,且大於最小的大小
- 塊是否在
inuse
狀態
進入 fastbin
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())) {
if (have_lock
|| ({ assert (locked == 0);
mutex_lock(&av->mutex);
locked = 1;
chunk_at_offset (p, size)->size <= 2 * SIZE_SZ // next->size <= 2 * SIZE_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem; //
}))
{
errstr = "free(): invalid next size (fast)";
goto errout;
}
set_fastchunks(av);
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
mchunkptr old = *fb, old2;
unsigned int old_idx = ~0u;
do
{
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->fd = old2 = old; // 插入 fastbin
}
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;
}
}
如果 size
滿足 fastbin
的條件,則首先判斷 next_chunk->size
要滿足
next_chunk->size > 2 * SIZE_SZ
next_chunk->size < av->system_mem
接着就會找對相應的 fastbin
,然后插入 該 bin
的第一項。插入前有一個檢查
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
就是 p->size
索引到的 fastbin
的第一個指針不能和當前的 p
相同,否則會被認為是 double free
進入 Unsorted bin
如果被 free
的這個塊不是 通過 mmap
獲得的,就會進入下面的邏輯
else if (!chunk_is_mmapped(p)) {
if (! have_lock) {
(void)mutex_lock(&av->mutex);
locked = 1;
}
// 得到下一個 chunk 的指針
nextchunk = chunk_at_offset(p, size);
// 不能 free top chunk
if (__glibc_unlikely (p == av->top))
{
errstr = "double free or corruption (top)";
goto errout;
}
// nextchunk 不能越界,就是限制了 p->size
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
{
errstr = "double free or corruption (out)";
goto errout;
}
/*p 要被標識為 inuse 狀態 */
if (__glibc_unlikely (!prev_inuse(nextchunk)))
{
errstr = "double free or corruption (!prev)";
goto errout;
}
nextsize = chunksize(nextchunk);
// nextsize 在 [ 2 * SIZE_SZ, av->system_mem] 之間
if (__builtin_expect (nextchunk->size <= 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);
/* 如果 p的前一個塊是 free 狀態,就向前合並,通過 p->pre_inused 判斷*/
if (!prev_inuse(p)) {
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
if (nextchunk != av->top) {
// 獲得 nextchunk 的下一個 chunk, 的 pre_inused位
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
// 如果 nextchunk 也是 free 狀態的,合並
if (!nextinuse) {
unlink(av, nextchunk, bck, fwd);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);
// 合並的結果放置到 unsorted bin
bck = unsorted_chunks(av);
fwd = bck->fd;
// 防止 unsortedbin 被破壞
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);
}
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
check_chunk(av, p);
}
// 如果 free 得到的 unsorted bin 的 size(包括合並chunk 得到的) 大於等於 FASTBIN_CONSOLIDATION_THRESHOLD 就會觸發 malloc_consolidate
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 {
/* Always try heap_trim(), even if the top chunk is not
large, because the corresponding heap might go away. */
heap_info *heap = heap_for_ptr(top(av));
assert(heap->ar_ptr == av);
heap_trim(heap, mp_.top_pad);
}
}
if (! have_lock) {
assert (locked);
(void)mutex_unlock(&av->mutex);
}
}
/*
If the chunk was allocated via mmap, release via munmap().
*/
大概流程
- 首先做了一些檢查,
p != top_chunk
,p->size
不能越界, 限制了next_chunk->size
,p
要處於inuse
狀態(通過next_chunk->pre_inused
判斷) - 接着判斷
p
的前后相鄰塊是不是free
狀態,如果是就合並 - 根據此次拿到的
unsorted bin
的 大小,如果size>=FASTBIN_CONSOLIDATION_THRESHOLD
就會觸發malloc_consolidate
如果 p
是通過 mmap
獲得的,就通過
munmap_chunk (p);
釋放掉他
Check In Glbc
函數名 | 檢查 | 報錯信息 |
---|---|---|
unlink | p->size == nextchunk->pre_size | corrupted size vs prev_size |
unlink | p->fd->bk == p 且 p->bk->fd == p | corrupted double-linked list |
_int_malloc | 當從fastbin分配內存時 ,找到的那個fastbin chunk的size要等於其位於的fastbin 的大小,比如在0x20的 fastbin中其大小就要為0x20 | malloc():memory corruption (fast) |
_int_malloc | 當從 smallbin 分配 chunk( victim) 時, 要求 victim->bk->fd == victim | malloc(): smallbin double linked list corrupted |
_int_malloc | 當迭代 unsorted bin 時 ,迭代中的 chunk (cur)要滿足,cur->size 在 [2*SIZE_SZ, av->system_mem] 中 | malloc(): memory corruption |
_int_free | 當插入一個 chunk 到 fastbin時,判斷fastbin的 head 是不是和 釋放的 chunk 相等 | double free or corruption (fasttop) |
_int_free | 判斷 next_chunk->pre_inuse == 1 | double free or corruption (!prev |
各種漏洞原理及利用
通用的信息泄露思路
當 chunk
處於 free
狀態時,會進入 bin
里面,其中的 fd
和 bk
可以用於信息泄露
- 分配兩個
0x90
的chunk(p0, p1)
- 釋放掉
p0
,p0
會進入unsorted bin
- 分配
0x90
的chunk
,再次拿到p0
, 在malloc
的實現中不會對這些指針進行清空,就可以泄露
如果分配后的內存被 memset
清空后,就需要利用一些其他的漏洞才能利用。
Unsorted bin
用於泄露libc
fastbin
用於 泄露heap
地址
Unlink 利用
原理
在把 chunk
從 bins
拿下來時 會觸發 unlink
操作
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (P->size) \
&& __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 (check_action, \
"corrupted double-linked list (not small)", \
P, AV); \
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; \
} \
} \
} \
}
如果我們可以偽裝 fd
和 bk
過掉 unlink
的檢查,就可以實現 4
字節寫
利用
首先利用其它的漏洞偽造下面的內存布局
- p0 = malloc(0x80), p1 = malloc(0x80), ptr = p0
- 此時
free(p1)
,發現p1
所在chunk
的pre_size = 0
, 表明前一個chunk
已經free
, 於是向前合並 - 通過
p1 - 0x10 - 0x80
( chunk_addr - pre_size ) , 找到前面已經釋放的chunk
, 也就是 我們 偽造的fake chunk p1
- 然后進行
unlink
, 實現*ptr = ptr-0x18
Fastbin Attack 總結
原理
Fastbin
在分配 chunk
時,只檢查 p->size&0xfffffffffffff000是否滿足等於的 fastbin的大小 ,而且不檢查指針是否對齊。所以我們只要找到 size
為 fastbin
的范圍,然后修改 位於 fastbin
的 chunk
的 fd
到這 ,分配幾次以后,就可以分配到這個位置
利用方式
利用 libc 中的 現有的 數據
__malloc_hook 附近
64位下在 __malloc_hook - 0x23 + 0x8 處 的值 為 p64(0x7f)
,這些值可以通過 gdb + hexdump
找找
然后想辦法修改 位於 0x70
的 fastbin
的 chunk
的 fd
為 __malloc_hook - 0x23,然后分配幾次 0x70 的 chunk 就可以修改 __malloc_hook
main_arean->fastbinY 數組
該數組用於存放 指定大小的 fastbin
的表頭指針,如果為空則為 p64(0)
, 而堆的地址基本 是 0x5x
開頭的(其在內存就是 xx xx..... 5x
), 此時如果在 main_arean->fastbinY
的 相鄰項為 0x0
(相鄰大小的 fastbin
), 就會出現 5x 00 00 00...
, 所以就可以出現 0x000000000000005x
,可以把它作為 fastbin
的 size
進行 fastbin attack
,不過作為 fastbin attack 的 size 不能 為 0x55
於是想辦法修改 位於 0x50
的 fastbin
的 chunk
的 fd
為 __malloc_hook - 0x23,然后分配幾次 0x50
的 chunk 就可以分配到 main_arean
, 然后就可以修改 main_arean->top
。
std* 結構體
在 std*
類結構體中有很多字段都會被設置為 0x0 , 同時其中的某些字段會有 libc 的地址大多數情況下 libc 是加載在 0x7f....
, 配合着 std*
中的 其他 0x0 的字段,我們就可以有 p64(0x7f)
, 然后修改 位於 0x70
的 fastbin
的 chunk
的 fd
為該位置即可。
自己構造 size
利用 unsorted bin attack 往 __free_hook 構造 size
我們知道如果我們可以 修改 unsorted bin
的 fd
和 bk
, 在對 unsorted bin
拆卸的 時候 我們就能實現
*(bk + 0x10) = main_arean->unsorted_bin
利用這個我們就能往任意地址寫入 main_arean
的地址, 由於 libc
的地址基本都是 0x7fxxxxx
, 所以寫完以后我們就可以在 __free_hook
的前面構造出 p64(0x7f)
, 可以作為 fastbin attack
的目標,然后修改 __free_hook
有一個小坑要注意,在 __free_hook-0x30
開始 的 0x30
個字節 是 _IO_stdfile_*_lock
區域,用於 std*
類文件的鎖操作,這個區域的內存會被經常清零。
所以 unsorted bin attack
應該往上面一點, 比如 libc.symbols['__free_hook'] - 0x50
還有一點就是在進行 unsorted bin attack
以后 , unsorted bin
鏈表就被破壞了,所以 就只能通過 fastbin
或者 smallbin
進行內存的分配,所以我們應該先劫持 fastbin
的 fd
到 目標位置,然后觸發 unsorted bin attack
寫入 size
, 最后進行 fastbin attack
,修改 __free_hook
利用 fastbin 往 main_arean 構造 size
-
首先分配
0x40
的chunk p
, 然后釋放掉p
,進入0x40
的fastbin
-
然后通過一些手段,修改
p->fd = p64(0x71)
-
分配
0x40
的chunk
,會拿到p
, 此時main_arean->fastbinY
中0x40
大小對應的項的值為p64(0x71)
-
然后分配
0x71
的chunk p2
, 釋放掉 -
修改
p2->fd
為main_arean->fastbinY
的相應位置,然后分配兩次,即可分配到main_arean->fastbinY
-
然后通過修改
main_arean->top
, 即可分配到 malloc_hook 或者 free_hook 等
Unsorted bin Attack
原理
因為 unsorted bin
的取出操作沒有使用 unlink
宏,而是自己實現的幾行代碼
bck = victim->bk;
...
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
所以當我們控制了 victim的 bk 時,則 bk + 0x10
會被改寫成 unsorted bin 的地址,但是 unsorted bin
的 bk
也會被破壞,下一次再到這里時就可能因為 victim->bk->fd
不可寫而造成 SIGSEGV
。
所以在觸發 unsorted bin attack
以后就 只能 通過 fastbin 和 smallbin 來分配內存了(否則會進入 unsorted bin 的流程,會報錯),所以在 觸發 unsorted bin attack
需要把需要的內存布局好。
利用的方式
寫 stdin->_IO_buf_end
在 glibc中 scanf, gets 等函數默認是對 stdin 結構體進行操作。以 scanf
為例
- 在調用
scanf
獲取輸入時,首先會把輸入的東西復制到[_IO_buf_base , _IO_buf_end ]
, 最大大小為_IO_buf_end - _IO_buf_base
。 - 修改
unsorted bin
的bck
為_IO_base_end-0x10
,就可以使_IO_base_end=main_arens+0x88
,我們就能修改很多東西了,而且malloc_hook
就在這里面。
__IO_list_all 和 abort 以及 修改虛表到 _IO_wstrn_jumps
原理
繞過虛表校驗
其實就是對 house of orange
在 libc2.24
里面的再利用。 在 libc2.24
里對 vtable
進行了校驗。
對 vtable
進行校驗的函數是 IO_validate_vtable
就是保證 vtable
要在 __stop___libc_IO_vtables
和 __start___libc_IO_vtables
之間。
這里的目標就是 _IO_wstrn_jumps
,這個也是一個 vtable
,能夠滿足 IO_validate_vtable
的校驗。
在 _IO_wstrn_jumps
有一個有趣的函數 IO_wstr_finish
, 位於 libc.symbols['_IO_wstrn_jumps'] + 0x10
void __fastcall IO_wstr_finish(_IO_FILE_2 *fp, int dummy)
{
_IO_FILE_plus *fp_; // rbx
wchar_t *io_buf_base; // rdi
fp_ = fp;
io_buf_base = fp->_wide_data->_IO_buf_base;
if ( io_buf_base && !(fp_->file._flags2 & 8) )
(fp_[1].file._IO_read_ptr)(io_buf_base, *&dummy); // call qword ptr [fp+0E8h]
fp_->file._wide_data->_IO_buf_base = 0LL;
_GI__IO_wdefault_finish(fp_, 0);
}
我們把 fp->_wide_data
改成 fp
, 然后設置 fp->_IO_buf_base
設置為 /bin/sh
的地址,fp_[1].file._IO_read_ptr
( fp+0xe8 ) 改成 system
的地址,其他字段根據 check
設置好以便過掉檢查, 之后調用該函數就會 system('/bin/sh')
利用方案舉例
以 34c3ctf-300 為例, 程序限制只能分配 0x310
的 chunk
, 這里利用 unsorted bin
遍歷的缺陷,偽造了一個 0x60
的 smallbin
,為后續做准備。
- 首先分配 4個
0x310
的chunk (A X B K)
,釋放A , B
此時A , B
均進入unsorted bin
,並且通過bk
鏈接起來 - 修改
A->bk
為fake_bin
的地址,並且 設置 fake_bin->size=0x61 and fake_bin->bk = B, 此時unsorted bin
的鏈表其實有 3 項。 - 分配 一個
0x310
的chunk
,此時 A 位於鏈表首部,且大小剛好,分配 A ,並且 把 fake_bin 置於鏈表首部 - 再次分配 一個
0x310
的chunk
, 此時 fake_bin 位於鏈表首部,大小不夠於是把 fake_bin 放到 smallbin[4] , 然后繼續遍歷 ,分配到B
, 至此 在 smallbin[4] 就存有 fake_bin 的地址
fake_bin 的 內容為 (從 chunk 的開始地址開始
payload = p64(0xfbad2084) #偽造的 File 結構體的開始,fp->_flag
payload += p64(0x61)
payload += p64(0xb00bface)
payload += p64(B_addr) # bk ,設置為 B 的地址
payload += p64(0x0) # fp->_IO_write_base
payload += p64(libc_base + sh_addr) # fp->_IO_write_ptr
payload += p64(libc_base + sh_addr) # fp->wide_data->buf_base
payload += "A"*60
payload += p64(0x0) # fp->_flags2
payload += "A"*36
payload += p64(fake_bin) # fp->_wide_data ,設置為 fake_bin, 復用 fake_bin
payload += "A"*24
payload += p64(0x0) # fp->_mode
payload += "A"*16
payload += p64( libc.symbols['_IO_wstrn_jumps'] + 0x10 -0x18) # fake vtable
payload += "A"*8
payload += p64(libc_base + libc.symbols['system']) # ((_IO_strfile *) fp)->_s._free_buffer
- 然后利用
unsorted bin attack
修改__IO_list_all
為main_arean+88
- 觸發
abort
(malloc_printerr內部會調用), 就會觸發 _IO_flush_all_lockp
,根據__IO_list_all
和__chain
,遍歷調用_IO_OVERFLOW (fp, EOF)
( 其實就是 (fp->vtable + 0x18)(fp, EOF) ___IO_list_all->_chain
位於smallbin[4]
,所以遍歷第二次可以對fake_bin
進行_IO_OVERFLOW (fp, EOF)
,此時就會調用 IO_wstr_finish, 此時 fake_bin 中的相關數據已經設置好,最后會執行 system("/bin/sh")
參考
34c3ctf-300
Pwn with File結構體 四
組合 fastbin attack
方案一
- 把
bk
改成global_max_fast-0x10
觸發unsorted bin attack
后,global_max_fast
會被修改成一個很大的值(指針),所以之后的 內存 分配 和 釋放 都會按fastbin
來 - 之后看情況進行 偽fastbin attack
方案二
把 bk
改成 libc.symbols['__free_hook'] - 0x50 觸發 unsorted bin attack
后, free_hook
前面就會出現 p64(0x7f)
,之后就可以通過 fastbin attack
修改 free_hook
結合 largebin 和 _dl_open_hook
原理
在 遍歷 unsoted bin
時, 是通過 bk 指針 進行遍歷
for (;; )
{
int iters = 0;
//victim = unsorted_chunks (av)->bk
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 遍歷 unsorted bin
{
bck = victim->bk;
............
............
............
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck; //unsorted_chunks (av)->bk = victim->bk->bk
bck->fd = unsorted_chunks (av);
......
......
......
}
所以通過修改 bk
來偽造 unsorted bin
是可行的
同時在 遍歷 unsorted bin
把 chunk
放入 largebin
的過程中, 也沒有什么檢查,於是可以利用 把 chunk
放入 largebin
的過程 往 任意地址寫入 chunk 的地址。
PS: 因為要偽造 unsorted bin ,需要我們可以拿到 heap 的基地址
大體的思路
- 在堆上通過修改
unsorted bin
的bk
指針 偽造幾個unsorted bin(A B C D),(0x400, 0x30, 0x420, 0x30)
- 分配
0x30
, A 進入 largebin, B 被分配 - 修改 A->bk = _dl_open_hook - 0x10 and A->bk_nextsize = _dl_open_hook - 0x20
- 分配
0x30
,C
進入largebin
, 會導致A->bk->fd = C , A->bk_nextsize->fd_nextsize = C
(其實就是 *_dl_open_hook = C) - 此時
_dl_open_hook
指針被改成C
的地址, 然后在C
中設置 p64(libc.symbols['__libc_dlsym'] + 4)+p64(one_gadget)+p64(one_gadget) , 偽造dl_open_hook
結構體。 - 后面的執行過程會調用
_dl_open_hook
, 就會調用__libc_dlsym + 4
, 這里面會 跳轉到dl_open_hook
結構體偏移 8 的值處 , 也就是one_gadget
的地址
參考
0ctf 2018 babyheap challenge exploit
特定寫權限的利用
可寫 main_arean
通過一些 fastbin
攻擊, 我們可以分配到 main_arean
, 此時一般都是改寫 main_arean->top
轉換為寫 __malloc_hook
malloc_hook -0x10 處存放的是指針,值很大,修改 main_arean->top 到這里,然后控制程序 使得通過 top_chunk
分配, 就可以分配到 malloc_hook
轉換為寫 __free_hook
在 free_hook-0xb58
處存放的也是一些地址,修改 main_arean->top 到這里,然后控制程序 使得通過 top_chunk
分配幾次內存(一次分配太多,會觸發 sysmalloc
, 可以一次分配 0x90 多分配幾次),我們就可以分配到 free_hook
可寫 __malloc_hook
直接寫one_gadget
寫入 one_gadget
,不過觸發的時候,用 malloc_printerr
來觸發 malloc
此時用下面這樣的 one_gadget
[rsp+0x50]
0xef6c4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
這樣更穩定,成功率也高
通過 __realloc_hook 中轉
__malloc_hook
和 __realloc_hook
是相鄰的, 且 __realloc_hook
在 __malloc_hook
的前面,所以基本上可以同時修改它們。
利用 one_gadget
時,對於棧的條件會有一些要求,利用 realloc
函數內部的 跳轉 到 __realloc_hook
之前的棧操作,加上棧中原有的數據,可以對棧進行跳轉,以滿足 one_gadget
的要求
realloc proc near ; DATA XREF: LOAD:0000000000006BA0↑o
push r15
push r14
push r13
push r12
mov r13, rsi
push rbp
push rbx
mov rbx, rdi
sub rsp, 38h
mov rax, cs:__realloc_hook_ptr #取出 __realloc_hook 指針
mov rax, [rax]
test rax, rax
jnz loc_848E8
test rsi, rsi
jnz short loc_846F5
test rdi, rdi
jnz loc_84960
代碼中的 push
以及 sub rsp, 38h
都可用於對棧進行調整。
可以收先把 __malloc_hook
設置為 0x6363636363636363
, 當程序斷下來后,查看棧的情況,然后選擇跳轉的位置。
最后把 malloc_hook
設置為選擇好的位置,realloc_hook
設置為 one_gadget
, 觸發 malloc
可寫 __free_hook
直接寫one_gadget
改成 system 函數的地址
然后 釋放掉 內容為 /bin/sh\x00
的 chunk
可寫 std* 結構體
std*
類結構體 定義是 _IO_FILE_plus
, 64
為大小為 0xe0
修改 vtable指針
libc <= 2.23
_IO_FILE_plus
的最后一個字節就是 vtable
指針,修改 vtable
指針到一個可控數據可控的地址,在地址處填上 one_gadget
, 然后在調用一些輸入輸出函數時,就會觸發。
如果是堆類題目可以 修改vtable指針到 heap, 或者如果是通過 fastbin 攻擊
分配到了 std*
, 那么可以修改 vtable 到 std 的相應位置*, 只要保證 馬上要被調用的函數指針我們可控 即可
libc > 2.23
一般結合 unsorted bin attack
,改到 libc.symbols['_IO_wstrn_jumps'] + 0x10 -0x18, 然后觸發 abort
會調用 _IO_OVERFLOW (fp, EOF)
時就會調用 IO_wstr_finish(fp, EOF)
,通過設置 fp
的數據,就可以 system("/bin/sh")
.
(: fp為文件結構體的指針
Double Free
原理
程序把指針 free
之后沒有對指針進行清空, 出現了 懸垂指針。后續還可以對該指針進行 free
操作。
利用
基於 pwnable.tw 中的 secretgard
信息泄露
總的思路 : 大塊 拆成 小塊
- 分配一個
0x120
的chunk
,p1
指向 它。 然后釋放掉他 - 分配兩個
0x90
的chunk
重用剛剛free
掉的chunk
, 可以發現此時p1==p2
- 此時再次
free(p1)
, 在p2->fd
和p2->bk
會寫入main_arean
的地址(free
之后大小大於fastbin
的范圍,進入unsorted bin
) - 然后打印
p2
的內容就可以拿到libc
的地址
Overlap chunk + unlink
總的思路 : 小塊 融合成 大塊
- 首先分配兩個
0x90
的chunk (p0, p1)
,然后釋放掉,會進行合並,形成 一個0x120
的unsorted bin
- 然后分配一個
0x120
的chunk (p2)
, 則p0=p2
, 此時p0
所在的chunk
可以包含p1
的chunk
- 然后在
p0
所在的chunk
偽造一個free chunk
, 設置好fd
和bk
, 然后釋放p1
觸發unlink
add(0x80) # pz
add(0x80) # p0
add(0x80) # p1
add(0x80) # px
del(1)
del(2)
add(0x110) # p2
payload = p64(0) # p1's 用戶區
payload += p64(0x81) # fake chunk size
payload += p64(ptr - 0x18) # fd, ptr--->p0 + header_size
payload += p64(ptr - 0x10) # bk
payload += 'a' * (0x80 - len(payload))
payload += p64(0x80) # pre_size ----- 下一個 chunk p1
payload += p64(0x80) # size 設置 pre_inused=0
payload += 'b' * 0x70
payload += p64(0x80)
payload += p64(0x21) # size 設置 pre_inused=1 ---- p1-->next_chunk, 繞過 double free 檢查
edit(2,payload) # fake chunk
# p1 所在 chunk->pre_inused=0, 向前合並
# 觸發 fake chunk 的 unlink
# ptr--->p0 + header_size, 實現 *ptr = ptr-0x18
del(1)
修改 __malloc_hook
一般 malloc
觸發的方式,one_gadgets
由於限制條件不滿足,很可能會失敗
可以使用 malloc_printerr
觸發, 此時恰好 **[esp+0x50]=0 **
__malloc_hook - 0x23 + 0x8 的 內容為 0x000000000000007f , 可以用來繞過 fastbin
分配的檢查
可以 gdb + hexdump
找到類似的位置來偽造 fastbin
Overlap Chunk + Fastbin Attack
總的思路 : 小塊 融合成 大塊, 分配大塊操縱小塊
- 首先分配兩個
0x90
大小 的chunk (p0, p1)
- 釋放掉它們,合並成一個
0x120
的unsorted bin
- 分配
0x120
的chunk (p3)
, p3==p1, 而且此時通過p3
可以修改 p2 的 chunk ,Overlap Chunk完成 - 修改
p->size = 0x71
p = p2-0x10,p
為p2
所在chunk的地址 - 修改 p + 0x70 為 p64(0x70) + p64(0x41) , 設置 pre_inused =1, 使得后面 free(p2) 繞過
double free
檢測 - 此時
free(p2)
,p2
進入0x70
大小的fastbin
- 再次
free(p1)
(此時p1
所在chunk
的size
為0x120
), 得到一個0x120
的unsorted bin
- 再次分配
0x120
的chunk (p4)
, p4==p1 - 通過
p4
可以修改p2
指向的chunk
的fd
為__malloc_hook - 0x23
(此時p2
的chunk
已經在0x70
的fastbin
里面) - Fastbin Attack 開始,分配兩次,可以得到 p6 = __malloc_hook -0x13
- 然后修改 __malloc_hook
Overlap chunk + fastbin attack + 修改 top chunk
- 首先通過上面的
Overlap chunk
我們可以修改p2
的chunk
的內容 - 修改 chunk->size = 0x41 , 注意設置 好 chunk->nextchunk 的 pre_inused 位 避免過不了 double free 檢查
free(p2)
, 此時p2
的chunk
進入0x40
的fastbin
free(p3)
,malloc(0x110)
, 可以再次修改p2 chunk
, 修改 chunk->size = 0x41 and chunk->fd = 0x71malloc(0x30)
,此時 main_arean->fastbinY 中會有一項 的 值 為 p64(0x71)- 再次
free(p3)
,malloc(0x110)
,修改 p2 chunk, chunk->size = 0x71 free(p2)
, 此時p2
的chunk
進入0x70
的fastbin
free(p3)
,malloc(0x110)
,修改p2 chunk
, 設置chunk->size = 0x71 and chunk->fd = 0x40 fastbinY 的地址附近- 分配兩次
0x70
的chunk
, 可以修改 main_arean->top 為 __malloc_hook -0x10 (這里存的指針,值很大) - 然后使用
top chunk
進行分配, 就可以拿到__malloc_hook
Fastbin dup+ Fastbin Attack
在把釋放的塊放進fastbin
時,會檢測也 只檢測 當前 free 的 chunk 和 fastbin 第一項 是否相同 , 如果相同則報 double free
的錯誤。
- 首先 分配
2
個0x70
的chunk , p0, p1
- 釋放
p0
,p0
進入0x70
大小的fastbin
, 此時p0
為第一項 - 釋放
p1
,p1
進入0x70
大小的fastbin
, 此時p1
為第一項, p1->fd = p0 - 再次釋放
p0
, 此時p1
為fastbin
的 第一項,不會報錯,p0
進入fastbin
, 此時p0
為第一項 - 分配
0x70
的chunk p2
, p2==p0, 設置 p2->fd = __malloc_hook - 0x23,其實就是修改 p0->fd - 此時 __malloc_hook - 0x23 成為 0x70 fastbin 的第 3 項
- 分配三個 0x70 的 chunk p3, p4, p5, p5==__malloc_hook - 0x13
- 通過
p5
修改__malloc_hook
修改 __free_hook
因為 free_hook
上方很大一塊空間都是 \x00
, 所以使用 fastbin attack
直接來修改它基本不可能,可以迂回一下,在 free_hook-0xb58
位置會存一些指針,我們通過 fastbin attack
修改 main_arean->top
, 到這里然后多用 top_chunk
分配幾次,就可以分配到 free_hook
, 然后該 free_hook
為 system
。
Fastbin dup + Fastbin Attack 修改 main_arean->top
- 首先利用
Fastbin dup
我們可以拿到實現修改fastbin
中的塊的fd
- 由於在
fastbin
中 如果為空,其在main_arean->fastbinY
里面對應的值為0x0
, 而堆的地址基本 是0x5x
開頭的(其在內存就是xx xx..... 5x
), 此時如果在main_arean->fastbinY
的 相鄰項為0x0
, 就會出現5x 00 00 00...
, 所以就可以出現0x000000000000005x
,可以把它作為fastbin
的size
進行fastbin attack
,不過作為 fastbin attack 的 size 不能 為 0x55 - 然后我們就可以修改
main_arean->top
為free_hook-0xb58
- 之后多分配幾次, 既可以分配到
free_hook
- 改
free_hook
為system
free
掉一個 內容為/bin/sh\x00
的塊
修改 _IO_FILE_plus 結構體 的 vtable
在 libc 2.24
以下可修改 _IO_FILE_plus
的 vtable
指針到我們可控的位置,進行虛表的偽造。
off by one
原理
在一些情況下我們可以往指定的 buf
中多寫入 1
個字節的數據 ,這就是 off by one
. 這種情況下可以進行利用的原因在於 調用 malloc
分配內存是要 對齊的, 64 位 0x10 字節對齊, 32 位 8 字節對齊,下面均以64位進行說明。如果 malloc(0x28) 則會分配 0x30
字節的 chunk, 除去 0x10 的首部, 我們有 0x20 然后加上下一個 chunk 的 pre_size ,我們就有 0x28 了, 我們知道 pre_size
后面緊跟着就是 size
,所以利用 off by one
可以 修改 下一個 chunk
的 size
字段,同時 在 glibc 中的內存管理 非常依賴這個 size 字段,所以我們可以利用它做一些有趣的事情。
所以當程序中有類似這種不對齊的分配, 就要小心 off by one
利用
普通off by one
在這種情況下,溢出的那個字節不受限制,此時的利用思路就是,多分配幾個 chunk
,然后利用第一個來溢出修改第二個 chunk
的 size
(改大), 然后 free(chunk_2)
, 就可以 overlap chunk 3
, 要非常注意 in_used 位的設置
溢出 used 狀態的 chunk
在 free
時可以獲得包含 chunk
的 unsorted bin
溢出 free 狀態的 chunk
因為malloc
再分配內存時 不會校驗 unsorted bin
的 size
是否被修改
Glibc_Adventures-The_Forgotten_Chunks
基於 0ctf 2018 babyheap
信息泄露
- 首先malloc 4 個 chunk, malloc(0x18)
allocate(0x18) # 0, 0x20 chunk
allocate(0x38) # 1, 0x40 chunk----> 溢出修改為 0x91
allocate(0x48) # 2, 0x50 chunk
allocate(0x18) # 3, 0x20 chunk
-
然后在 chunk 0 溢出一個字節,修改 chunk 1 的 size 位 為 0x91 (原來應該為 0x41),這樣一來 通過
chunk 1
索引到的 下一個chunk
就是 p + 0x90 = chunk 3 (設p
為chunk 1
的地址) -
此時 釋放
chunk 1
,libc
會根據下一個chunk
(這里也就是 chunk3) 的pre_inused
位來檢查是否double free
, 由於chunk2
原來並沒有被釋放,所以pre_inused =1
,於是可以過掉檢查, 此時得到一個0x90
的unsorted bin
, 同時chunk2
在 這個unsorted bin
里面, overlap chunk 2 -
此時再次
malloc(0x38)
, 會使用unsorted bin
進行切割, 所以 在chunk 2
的 fd, bk 處會寫入 main_arean 的 地址, 打印chunk 2
的內容就可以leak libc
漏洞利用
其實可以 overlap chunk
了,就相當於獲得了 堆溢出
的能力,我們可以任意修改 chunk
的數據,此時可以使用 unlink
, unsorted bin attack
, fastbin attack
。 沒有限制內存分配的大小,使用 fastbin attack
即可
unlink
這種情況下的 unlink
應該比較簡單,在當前 chunk
偽造好 fd, bk
然后利用 off by one
修改 下一個 chunk
的 pre_size
(由於不對齊的分配,這個區域其實屬於當前 chunk ) 和 size
的 pre_inused
為 0
, 然后 free
掉下面那個 chunk
,就可以觸發 unlink
了
off by null
在這種情況下,我們只能溢出 \x00
字節, 所以會把 size
變小 同時 inused
位 會被設置為 0
unlink
B + 0x100
處要設置好 p64(xxx) + p64(0x41)
關鍵是 pre_inused 位 , free
的時候會檢測這個位
shrink free chunk size
布局過程
- 首先分配
3
個chunk (A B D)
, 大小分別為0x110 , 0x210, 0x110
- 然后 釋放
B
, 此時 D->pre_inused = 0 and D->pre_size = 0x210 - 修改
B+0x200
處 為p64(0x200)
,繞過新版 libc 的 chunksize(P) != prev_size (next_chunk(P)) 檢查
- 然后分配兩個
chunk (P, K)
, 大小為0x110, 0x90
- 釋放掉
P
, 此時P
會進入unsorted bin
, fd, bk 是有效的 , 原因是 后面合並 D 時需要 unlink - 釋放
D
, 發現D->pre_inused=0
, 說明前一個chunk
已經free
, 需要合並。 根據pre_size
找到P
, 然后unlink(P)
合並得到一個0x330
的unsorted bin
, 此時K
位於unsorted bin
內部, overlap chunk done
布局過程中的一些 tips
-
在第三步 ,釋放 B 之前把
B+0x200
處 設置p64(0x200)
, 因為新版的libc
會檢驗chunksize(P) != prev_size (next_chunk(P))
-
off by null
縮小B
以后,分配P
其大小不能再fastbin
的范圍內,后面釋放D
需要向前合並,會進行unlink
操作,所以大小 大於fastbin
,free(P)
后P
會進入unsorted bin
,此時他的fd
,bk
都是正常的,正常unlink
。
參考
how2heap
修改 pre_inused + 向前合並
方案一
- 首先分配
4
個chunk (A B C D)
, 大小分別為0x100, 0x100, 0x100, 0x80
. 最后那個用於防止top_chunk
合並 - 然后釋放
A
, 此時A
進入unsorted bin
, 生成了有效的FD
和BK
,為了可以在后面的融合中成功 unlink - 然后利用
off by null
, 設置 C 的 pre_size 和 pre_inused 。 - 釋放
C
, 系統 根據 C 的 pre_size 找到 A 進行合並,首先unlink(A)
因為A
已經在unsorted bin
,不會出錯,然后就會有一個0x300
的unsorted bin
, 此時B
位於 該unsorted bin
的 中間
方案二
如果程序限制只能在觸發 off by null
之后才能 釋放 A
,需要在 A
和 B
之間多分配一個內存塊 x(0x20), 原因是 觸發 off by null
后 B
被標識已經 free , 那么此時再 釋放 A
就會對 B
進行 unlink
,此時 B
中 fd
和 bk
是過不了 檢查的(B已經分配,並已經被用來進行 off by null ) 。
總結
對於堆相關的漏洞,不論是 堆溢出,double free, off by one ,uaf 等其最終目的都是為了修改 chunk
的一些管理結構 比如 fd,bk
, 然后在后續的堆管理程序處理中實現我們的目的(代碼執行)。
堆溢出
直接可以修改 下一個 chunk
的 元數據 ,然后就是 unsorteb bin attack
, fastbin attack
等攻擊手法了
double free
利用一些內存布局,可以實現 overlap chunk
,最后也是實現了 可以修改 chunk
的元數據
off by one
類似於 double free
,實現 overlap chunk
然后改 chunk
元數據