malloc源码分析之 ----- 你想要啥chunk

文章目录

  • malloc源码分析之 ----- 你想要啥chunk
    • tcache
    • fastbin
    • small bin
    • unsorted bin
    • bin处理
    • top

malloc源码分析之 ----- 你想要啥chunk

tcache

  1. malloc源码,这里以glibc-2.29为例:

    void * __libc_malloc (size_t bytes)
    {mstate ar_ptr;void *victim;void *(*hook) (size_t, const void *)= atomic_forced_read (__malloc_hook); // 检查malloc_hookif (__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);size_t tc_idx = csize2tidx (tbytes);MAYBE_INIT_TCACHE ();DIAG_PUSH_NEEDS_COMMENT;if (tc_idx < mp_.tcache_bins // 释放的chunk的 size在tcache的范围之内/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */&& tcache&& tcache->entries[tc_idx] != NULL) // 根据entries终端是否为空,来检查tcachce是否为空(这里不是根据count值来判断,从glibc-2.30开始才修改为用count来判断){return tcache_get (tc_idx); // 获取tcache中的chunk}DIAG_POP_NEEDS_COMMENT;
    #endifif (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 arenabefore.  */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;
    }// 在tcache 中取出chunk
    static __always_inline void * tcache_get (size_t tc_idx)
    {tcache_entry *e = tcache->entries[tc_idx];//拿出头chunkassert (tc_idx < TCACHE_MAX_BINS);assert (tcache->entries[tc_idx] > 0);tcache->entries[tc_idx] = e->next; // 更新头chunk--(tcache->counts[tc_idx]); // 数量减一e->key = NULL; // key字段清0 (用来检查double free)return (void *) e;
    }
    

    上面在tcache中取出chunk即tcache_get函数中,对取出的chunk本身的修改值局限于key字段清0 ,再无其他修改,申请出来的next字段没有清空,也没有任何检查。(在glibc-2.28以及之前,没有tcache的double free检查)

    这里存在两个利用:

  2. 没有UAF是泄漏heap地址:

    这里可以看见第一个chunk的next字段上有一个堆地址,但是此时chunk已经被释放,没有UAF漏洞的话无法泄漏地址:

    image-20240928094511695

    下面把这个chunk申请出来:

    这里更新新的链首:

    image-20240928095002589

    key字段清空:

    image-20240928095039382

    最后可以看到第一个chunk已经被申请出来,并且其上的next字段的堆地址并没有清空 ,所以此时没有UAF漏洞也能泄漏出堆地址:

    image-20240928095112981

  3. 任意地址申请chunk:从上面的代码中可以看出,tcache在拿出chunk时没有size检查

    将第一个chunk的next字段直接指向_IO_list_all即可申请到包含 _IO_list_all的chunk(不会有任何判断条件绕过):

    image-20240928095617006

    这里高版本会检查next上值的内存对齐问题,要按0x10对齐:

    image-20240928095731745

    还有一其他的利用就要结合free函数一起来:改掉mp_中的tcache_bins,来把更大的chunk放入tcache中管理。覆盖size字段 造成overlapping 等等利用:

    image-20240928100719737

fastbin

  1. 对fastbin中chunk的处理的部分,这里开始直接对照源码查看各个bin的处理顺序 :malloc.c - malloc/malloc.c - Glibc source code glibc-2.29:

    #define REMOVE_FB(fb, victim, pp)			do							{							victim = pp;					if (victim == NULL)				break;						}							while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) != victim);	if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) // 申请的大小在fastbin的范围之内{idx = fastbin_index (nb);mfastbinptr *fb = &fastbin (av, idx); // 拿到main_arena中的地址mchunkptr pp;victim = *fb; 	// 拿到对应的fastbin链中的头chunkif (victim != NULL) // 链不为空{if (SINGLE_THREAD_P)*fb = victim->fd;elseREMOVE_FB (fb, pp, victim); // 取出头chunkif (__glibc_likely (victim != NULL)){size_t victim_idx = fastbin_index (chunksize (victim));if (__builtin_expect (victim_idx != idx, 0)) // 联合上一句 进行size比较malloc_printerr ("malloc(): memory corruption (fast)");check_remalloced_chunk (av, victim, nb);
    #if USE_TCACHE/* While we're here, if we see other chunks of the same size,stash them in the tcache.  */ // 将相同大小的chunk放入到tacche中size_t tc_idx = csize2tidx (nb); // 拿到 在tcache中的下标if (tcache && tc_idx < mp_.tcache_bins){mchunkptr tc_victim;/* While bin not empty and tcache not full, copy chunks.  */while (tcache->counts[tc_idx] < mp_.tcache_count&& (tc_victim = *fb) != NULL) // 这里是用count来判断tcache是否放满{if (SINGLE_THREAD_P)*fb = tc_victim->fd;else{REMOVE_FB (fb, pp, tc_victim); // 从fastbin中移除if (__glibc_unlikely (tc_victim == NULL))break;}tcache_put (tc_victim, tc_idx); // 置入tcache中}}
    #endifvoid *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;}}}
    

可以看出,在申请出fastbin中的chunk时,仅存在一个victim_idx != idx 也就是size检查。并且在申请出fastbin后会将其fastbin链上的剩余chunk置入到tcache中:

fastbin中的几个利用:

  1. 结合free函数实现double free:free掉chunk在进入fastbin中时,只与链首比较来判断是否存在double free,而不像tcache那样额外设置一个key字段来比较,所以可以通过free chunk1 --> free chunk2 --> free chunk1 ,来造成chunk1的double free。

  2. 修改tls段上的global_max_fast值 ,来将较大的chunk放入fastbin中处理(这也要结合free函数),global_max_fast位于tls段上,有写的权限,所以可以任意地址申请chunk后将其改写(或者large bin attack、unsorted bin attack,能写一个较大的值就行):

    image-20240928103406015

    这里将global_max_fast,改为0xffff,然后将大小为0xd0的chunk置入到fastbin中:

    image-20240928103645594

    这里已经放入到fastbin中 了:

    image-20240928104146039

    再申请,这里用size和global_max_fast比较,通过后顺利进入到fastbin的处理中,实现了将更大的chunk放入fastbin进行处理 :

    image-20240928104645281

  3. 另外 fastbin中泄漏heap地址时,也存在与tcache类似的情况(没有UAF),原因就是将fastbin取出时,对取出的chunk没有任何修改:

    先释放两个chunk进入fastbin,第一个chunk上就会存在堆地址:

    image-20240928114644435

    再将其申请出来:

    这里对链首的处理仅仅只是更新main_arena中fastbinsY数组里面链首的地址,而对取出来的chunk没有任何处理:

    image-20240928115149715

    所以原理的对地址任然再里面:

    image-20240928114825202

  4. 将fastbin中剩余的chunk放入到tcache的过程中,对fastbin中的chunk没有检查(没有对size的检查),但是有tcache 了的话,一般不会用fastbin。

small bin

  1. 通过了分配之后就到small bin的检查:

      /*If a small request, check regular bin.  Since these "smallbins"hold one size each, no searching within bins is necessary.(For a large request, we need to wait until unsorted chunks areprocessed to find best fit. But for small ones, fits are exactanyway, so we can check now, which is faster.)*/if (in_smallbin_range (nb)) // 在small bin的范围之内{idx = smallbin_index (nb);bin = bin_at (av, idx); // 拿到main_arena中对应存放small bin的地址if ((victim = last (bin)) != bin) // 拿到size刚好符合的chunk{bck = victim->bk;if (__glibc_unlikely (bck->fd != victim)) // 双向链表检查malloc_printerr ("malloc(): smallbin double linked list corrupted");set_inuse_bit_at_offset (victim, nb);bin->bk = bck; // 从对应的small bin中移除bck->fd = bin;if (av != &main_arena)set_non_main_arena (victim);check_malloced_chunk (av, victim, nb);
    #if USE_TCACHE/* While we're here, if we see other chunks of the same size,stash them in the tcache.  */size_t tc_idx = csize2tidx (nb);if (tcache && tc_idx < mp_.tcache_bins) // 将对应的small bin中的chunk置入对应的tcache中{mchunkptr tc_victim;/* While bin not empty and tcache not full, copy chunks over.  */while (tcache->counts[tc_idx] < mp_.tcache_count&& (tc_victim = last (bin)) != bin) // tcache未满、small bin未空 就一直放入{if (tc_victim != 0){bck = tc_victim->bk;set_inuse_bit_at_offset (tc_victim, nb);if (av != &main_arena)set_non_main_arena (tc_victim);bin->bk = bck; // 从small bin中取出chunk 这里没有双向链表检查bck->fd = bin;tcache_put (tc_victim, tc_idx); // 置入到tcache中}}}
    #endifvoid *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;}}
    

    从small bin中申请chunk时,只有size刚好符合申请的大小,才会被选中(大小不适合不会被选中,在后面可能会被切割),并且在找到符合的size后,会将对应的small bin链上剩余的chunk 放入到tcache中,此时没有双向链表检查

  2. small bin利用,著名的 tcache stashing unlink attack ,利用上面的 small bin 进入tcache部分代码:

    准备

    image-20240928210352814

    修改small bin中的第2个chunk的bk字段(在small bin中是通过bk来索引chunk取出的,所以0x405250是第一个chunk),在用来覆盖bk字段的地址所代表的chunk的bk字段上要放上一个可写的地址

    image-20240928212937299

    下面申请一个与该small bin同大小的chunk(要绕过tcache 申请到small bin 即使用calloc函数,在glibc-2.30之前也可以通过控制对应的entries字段为空绕过tcache):

    image-20240928211145751

    这里通过双向链表检查,所以前面在覆盖时,不能损坏第二个chunk的fd字段上的值 ,不然双向链表检查无法通过:

    image-20240928211734991

    这里开始将该small bin中剩余的chunk 放入到tcache中(没有双向链表检查):

    image-20240928212039982

    先放入一个chunk:

    image-20240928212152775

    再向tcache中放入第二个chunk,也就是我们伪造的bk字段上代表的chunk:

    最后解链的时候,要向伪造的fake_chunk的bk字段上的地址 + 0x10 (之前要保证伪造的chunk的bk字段上要有一个可写的地址原因)处写上一个main_arena地址:

    image-20240928213837414

    最后在tcache中填入了一个地址 ,后续也能正常申请出来:

    image-20240928214416564

另外,在申请的chunk大小超过small bin的范围时,会先走下面这段代码:

  /*If this is a large request, consolidate fastbins before continuing.While it might look excessive to kill all fastbins beforeeven seeing if there is space available, this avoidsfragmentation problems normally associated with fastbins.Also, in practice, programs tend to have runs of either small orlarge requests, but less often mixtures, so consolidation is notinvoked all that often in most programs. And the programs thatit is called frequently in otherwise tend to fragment.*/else{idx = largebin_index (nb);if (atomic_load_relaxed (&av->have_fastchunks)) // 先判断fastbin中是否有空闲块,再决定是否将其移出malloc_consolidate (av);}

malloc_consolidate函数源代码如下,作用是将fastbin中的chunk整理到unsorted bin中

/*------------------------- malloc_consolidate -------------------------malloc_consolidate is a specialized version of free() that tearsdown chunks held in fastbins.  Free itself cannot be used for thispurpose since, among other things, it might place chunks back ontofastbins.  So, instead, we need to use a minor variant of the samecode.
*/static void malloc_consolidate(mstate av)
{mfastbinptr*    fb;                 /* current fastbin being consolidated */mfastbinptr*    maxfb;              /* last fastbin (for loop control) */mchunkptr       p;                  /* current chunk being consolidated */mchunkptr       nextp;              /* next chunk to consolidate */mchunkptr       unsorted_bin;       /* bin header */mchunkptr       first_unsorted;     /* chunk to link to *//* These have same use as in free() */mchunkptr       nextchunk;INTERNAL_SIZE_T size;INTERNAL_SIZE_T nextsize;INTERNAL_SIZE_T prevsize;int             nextinuse;atomic_store_relaxed (&av->have_fastchunks, false);unsorted_bin = unsorted_chunks(av); // 将unsorted bin取出的时候没有任何检查/*Remove each chunk from fast bin and consolidate it, placing itthen in unsorted bin. Among other reasons for doing this,placing in unsorted bin avoids needing to calculate actual binsuntil malloc is sure that chunks aren't immediately going to bereused anyway.*/maxfb = &fastbin (av, NFASTBINS - 1);fb = &fastbin (av, 0); // 找到最小的fastbin链do {p = atomic_exchange_acq (fb, NULL); // 取出并移出fastbinif (p != 0) {do {{unsigned int idx = fastbin_index (chunksize (p));if ((&fastbin (av, idx)) != fb) // size检查malloc_printerr ("malloc_consolidate(): invalid chunk size");}check_inuse_chunk(av, p);nextp = p->fd;/* Slightly streamlined version of consolidation code in free() */ // 开始检查合并size = chunksize (p);nextchunk = chunk_at_offset(p, size); // 相邻的高地址处的chunk --> chunk¥nextsize = chunksize(nextchunk);// 下面的合并过程同 free函数中,对要进入unsorted bin的chunk进行合并if (!prev_inuse(p)) { // 向后合并 (向低地址)prevsize = prev_size (p);size += prevsize;p = chunk_at_offset(p, -((long) prevsize)); // 找到相邻的低地址chunk --> chunk@if (__glibc_unlikely (chunksize(p) != prevsize)) // 将 chunk@ 与 前面取出的chunk的prev_size位比较malloc_printerr ("corrupted size vs. prev_size in fastbins");unlink_chunk (av, p); // 将前一个chunk@ 解链取出}if (nextchunk != av->top) { // 后面是top chunk 则直接融入topnextinuse = inuse_bit_at_offset(nextchunk, nextsize);if (!nextinuse) { // 向前合并(向高地址)size += nextsize; // 向前合并时 没有prev_size 和 chunksize的比较unlink_chunk (av, nextchunk); // 直接解链取出 相邻的高地址的chunk¥} elseclear_inuse_bit_at_offset(nextchunk, 0); // 如果chunk¥在被使用,就清空标志位即可first_unsorted = unsorted_bin->fd;// 拿出unsorted bin中的原链首chunkunsorted_bin->fd = p; // 更新unsorted bin的链首first_unsorted->bk = p; // 将原来的链首作为第二个链入if (!in_smallbin_range (size)) { // large bin chunk的fd_nextsize、bk_nextsize 处理p->fd_nextsize = NULL;p->bk_nextsize = NULL;}set_head(p, size | PREV_INUSE); // 更新sizep->bk = unsorted_bin; // 建立双向链表p->fd = first_unsorted;set_foot(p, size); // 更新下一个chunk的prev_size = size}else {size += nextsize;set_head(p, size | PREV_INUSE);av->top = p;}} while ( (p = nextp) != 0); // 循环直到该fastbin链全部取完 将一个fastbin中的chunk,全部链入到small bin中}} while (fb++ != maxfb); // 依次找size更大的fastbin链
}

调试走一下逻辑:

先准备3个fastbin中的chunk,随后申请大于small bin范围的chunk,就能走到malloc_consolidate函数这里:

image-20240929112652503

这里通过检查后会进入到malloc_consolidate函数:

image-20240929113137745

从小的chunk开始,将fastbin中的chunk置入到small bin中,这里取出chunk_0x20,首先对取出的chunk的size进行检查

如果size不属于该fastbin链,就会直接报错退出:

image-20240929113704995

从fastbin中取出chunk后,开始检查合并:

这里检查向后合并,随后检查向前合并

image-20240929114523266

这里放入到unsorted bin中,后续再建立双向链表,取出的chunk正式进入到unsorted bin中:

image-20240929115512475

image-20240929115636741image-20240929115713125

随后去取下一条fastbin链,再重复上面的操作:

image-20240929120245686

利用1:这里有unlink,肯定存在一些利用,下面利用fastbin 结合 malloc函数来造unlink 实现overlapping(之前接触的到的unlink都是free函数结合 较大的chunk来实现unlink --> overlapping):

这里伪造的chunk如下:

将chunk1的fd 和 bk 填上相应的堆地址(后续用来过unlink检查),将chunk2释放进入到fastbin中,并伪造好prev_inuse位和prev_size字段

image-20240929122517366

随后申请一个大于small bin的chunk,进入到malloc_consolidate函数中:

image-20240929123030787

这里将chunk2取出:

image-20240929123253724

随后检查size字段:

image-20240929123336838

随后是unlink的重点,检查chunk2的prev_inuse位 --> 进而判断前一个chunk是否被释放:

image-20240929123428212

然后,再检查chunk2的prev_size和chunk1的size是否相同,随后进入到unlink中:

image-20240929123627502

unlink中的prev_size和chunksize检查,单针对要合并的那个chunk1:

image-20240929123810704

对要合并的chunk1进行双向链表检查

image-20240929123914224

最后unlink结束,malloc_consolidate函数后续将两个chunk合并,并一起放入到unsorted bin中 ,至此完成了用malloc 函数实现 unlink的操作:

image-20240929124324171

image-20240929124421431

最后malloc函数执行完后,该合并后的chunk会放入到small bin中(后续unsorted bin会解释):

image-20240929124643398

利用2 :利用fastbin + small bin 不用溢出到下一个chunk的prev_inuse位即可完成overlapping(一直到2.40都可使用)

先申请好chunk,申请好一个small bin chunk(提供一个天然的prev_size=0,并且在修改了他的prev_size字段在malloc函数中不会有检查)和 一个fastbin chunk(触发合并):

image-20241004183355319

伪造,prev_size字段,和对应的合并的chunk的size、fd、bk字段:

image-20241004183523085

最后申请一个不属于small bin的chunk,就能完成合并:

进入malloc_consolidate函数来整合fastbin中的chunk:

image-20241004183644828

拿出了准备在fastbin中的chunk后,进行合并,先检查该chunk的prev_inuse位(不是我们伪造的),来看相邻低地址的chunk是否被释放:

image-20241004183822637

这里用我们伪造的prev_size和fake_chunk的size进行比较,随后顺利进入解链:

image-20241004183944866

unlink解链,两个检查都能绕过:

image-20241004184042674

image-20241004184059311

最后完成 malloc_consolidate函数顺利overlapping,进入到unsorted bin中:

image-20241004184215224

后续会对unsorted bin中的chunk处理,看看合并后的chunk能不能绕过:

这里对unsorted bin中取出的chunk检查了 prev_size字段 和size字段,前面合并后的chunk能顺利绕过。但是前面那个small bin中的chunk如果是在unsorted bin中的话,由于我们修改了prev_size字段,所以这里处理时会报错(因此前面才使用small bin中的chunk)

image-20241004184520229

最后合并的chunk顺利进入到small bin中,造成overlapping(没有修改prev_inuse位):

image-20241004184815956

unsorted bin

  1. malloc后续进入到对unsorted bin的处理:

     /*Process recently freed or remaindered chunks, taking one only ifit is exact fit, or, if this a small request, the chunk is remainder fromthe most recent non-exact fit.  Place other traversed chunks inbins.  Note that this step is the only place in any routine wherechunks are placed in bins.The outer loop here is needed because we might not realize untilnear the end of malloc that we should have consolidated, so mustdo so and retry. This happens at most once, and only when we wouldotherwise need to expand memory to service a "small" request.*/#if USE_TCACHEINTERNAL_SIZE_T tcache_nb = 0;size_t tc_idx = csize2tidx (nb);if (tcache && tc_idx < mp_.tcache_bins) // 如果申请的大小在tcache的范围之内tcache_nb = nb; // 初始化tcache_nbint return_cached = 0; // 初始化return_cachedtcache_unsorted_count = 0; // 在处理unsorted bin之前将 tcache_unsorted_count清0
    #endiffor (;; ) // 这个for循环囊括了后续所有的处理{int iters = 0;while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 将unsorted bin遍历完{bck = victim->bk;size = chunksize (victim);mchunkptr next = chunk_at_offset (victim, size);if (__glibc_unlikely (size <= 2 * SIZE_SZ)|| __glibc_unlikely (size > av->system_mem))malloc_printerr ("malloc(): invalid size (unsorted)");if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))malloc_printerr ("malloc(): invalid next size (unsorted)");if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");if (__glibc_unlikely (bck->fd != victim) // 双向链表检查|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))malloc_printerr ("malloc(): unsorted double linked list corrupted");if (__glibc_unlikely (prev_inuse (next))) // 检查unsorted bin中的chunk是否真的被标记为freemalloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");/*If a small request, try to use last remainder if it is theonly chunk in unsorted bin.  This helps promote locality forruns of consecutive small requests. This is the onlyexception to best-fit, and applies only when there isno exact fit for a small chunk.*/if (in_smallbin_range (nb) && // 申请的大小再small bin的范围之内bck == unsorted_chunks (av) && // unsorted bin中仅有这一个chunkvictim == av->last_remainder && // 是上次切割之后剩下的chunk(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // size要足够大,切割后能剩下一个chunk0x20{/* split and reattach remainder */remainder_size = size - nb;remainder = chunk_at_offset (victim, nb);unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; // 更新unsorted bin的链首av->last_remainder = remainder; // 给切割后剩下的chunk 标记remainder->bk = remainder->fd = unsorted_chunks (av); // 维护unsorted bin的双向链表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); // 给切割后剩下的chunk 新的sizeset_foot (remainder, remainder_size); // 新的prev_sizecheck_malloced_chunk (av, victim, nb);void *p = chunk2mem (victim); // 向用户类型转换alloc_perturb (p, bytes);return p; // 直接返回}/* remove from unsorted list */ // 移出unsorted binif (__glibc_unlikely (bck->fd != victim)) // 移除之前 先做双向链表检查malloc_printerr ("malloc(): corrupted unsorted chunks 3");unsorted_chunks (av)->bk = bck; // 更新unsorted bin 的链首 为其中的第二个chunkbck->fd = unsorted_chunks (av);/* Take now instead of binning if exact fit */if (size == nb) // 如果 移出的chunk的size刚好完美符合申请的大小{set_inuse_bit_at_offset (victim, size); // 标记为 正在被使用 --> 相当于直接申请到了该chunkif (av != &main_arena)set_non_main_arena (victim);
    #if USE_TCACHE/* Fill cache first, return to user only if cache fills.We may return one of these chunks later.  */ // 首先用来填充tcache、如果tcache满了的话才返回给用户if (tcache_nb && tcache->counts[tc_idx] < mp_.tcache_count){tcache_put (victim, tc_idx);return_cached = 1; // 标记一下tcache现在 可以返回的chunk 等这个while循环对unsorted bin处理完之后 直接用tcache来返回continue; // 结束这次处理}else{
    #endifcheck_malloced_chunk (av, victim, nb); // 如果没有使用tcache(即在glibc-2.26以前) 那么直接返回刚才移出unsorted bin的chunkvoid *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;
    #if USE_TCACHE}
    #endif}/* place chunk in bin */if (in_smallbin_range (size)) // chunk的size 在small bin的范围内{victim_index = smallbin_index (size); // 通过size找到要置入的small bin链 在main_arena.bins数组中的下标bck = bin_at (av, victim_index); // 通过下标 找到对应的 small bin链fwd = bck->fd; // 拿到链首}else // chunk的size 在large bin的范围内{victim_index = largebin_index (size); // 找large bin链 在main_arena.bins数组中的下标bck = bin_at (av, victim_index); // 找到链首fwd = bck->fd;/* maintain large bins in sorted order */ // 要保证large bins中的chunk处于有序的状态下if (fwd != bck) // 找到的 large bin链非空{/* Or with inuse bit to speed comparisons */size |= PREV_INUSE;/* if smaller than smallest, bypass loop below */assert (chunk_main_arena (bck->bk));if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) // 取出的chunk 小于当前large bin中最小的chunk时{fwd = bck;bck = bck->bk; // large bin中最小的chunk// 将取出的chunk链入到 large bin中 (更新fd\bk_nextsize字段)保证large bin处于有序状态victim->fd_nextsize = fwd->fd; // fwd->fd的值是该large bin链上最大的chunk地址 victim是新的最小chunk,最小的chunk的fd_nextsize字段 要指向size最大的chunkvictim->bk_nextsize = fwd->fd->bk_nextsize; // fwd->fd->bk_nextsize是原链中最小size的chunk,现在排在victim前面fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; // 更新原来最小chunk前后(指size大小)chunk的fd\bk_nextsize}else // size不小于当前large bin中的最小chunk (这部分在glibc-2.31之后新增调表检查(对fd\bk_nextsize检查),所以就不在利用){assert (chunk_main_arena (fwd));while ((unsigned long) size < chunksize_nomask (fwd)){fwd = fwd->fd_nextsize;assert (chunk_main_arena (fwd));}if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd))/* Always insert in the second position.  */fwd = fwd->fd;else{victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;}bck = fwd->bk;}}else // 找到的 large bin链是空的 直接用chunk本身来填充fd\bk_nextsize字段victim->fd_nextsize = victim->bk_nextsize = victim;}// 最后无论是放入small bin 还是 large bin都会进行下面这段处理mark_bin (av, victim_index); // binmap标记一下 bin链上的情况(是否为空即没有一个空闲块),便于后续在main_arena.bins数组中快速寻找目标victim->bk = bck; // 插入 : 将取出的chunk置入对应的bin中victim->fd = fwd;fwd->bk = victim; // 更新 对应bin中的链首bck->fd = victim; #define mark_bin(m, i)    ((m)->binmap[idx2block (i)] |= idx2bit (i))#if USE_TCACHE/* If we've processed as many chunks as we're allowed whilefilling the cache, return one of the cached ones.  */++tcache_unsorted_count;if (return_cached&& mp_.tcache_unsorted_limit > 0&& tcache_unsorted_count > mp_.tcache_unsorted_limit){return tcache_get (tc_idx);}
    #endif#define MAX_ITERS       10000if (++iters >= MAX_ITERS)break;} // 这里结束上面的while 循环 对unsorted bin的处理// 上面对unsorted bin的处理结束
    #if USE_TCACHE/* If all the small chunks we found ended up cached, return one now.  */if (return_cached) // 之前标记的 tcache现在 可以返回的chunk 这里退出循环了之后直接返回{return tcache_get (tc_idx);}
    #endif
    ......
  2. 最开始时对last_remainder的处理过程:

    这里先构造一个 last_remainder,与unsorted bin中的第一个chunk相同:

    image-20240929162553432

    顺利通过四个条件判断,进入对last_remainder的处理:

    image-20240929164141173

    下面对last_remainder chunk进行切割:

    拿到新的remainder chunk的size、找到新的remainder chunk地址、更新unsorted bin的链首、为新的remainder chunk附上fd 和 bk

    image-20240929165013922

    更新申请到的chunk的size,更细新的remainder chunk的size,最后更新相邻高地址处的chunk的prev_size:

    image-20240929165255155

    最后从原last_remainder chunk中,切割出申请的chunk:

    image-20240929165410321

  3. 存在的利用:

    • 最后切割完成之后可以看到,在申请出来的chunk上存在main_arena地址,可以用来泄漏libc地址(在没有UAF时) ==> 很常用
  4. 下面对unsorted bin中的chunk处理:将其移出unsorted bin,置入到对应的bin(small 、large bin中):

    在将chunk置入到对应的bin里面之前,先对移出unsorted bin 的chunk进行下面的处理:

              /* Take now instead of binning if exact fit */if (size == nb) // 如果 移出的chunk的size刚好完美符合申请的大小{set_inuse_bit_at_offset (victim, size); // 标记为 正在被使用 --> 相当于直接申请到了该chunkif (av != &main_arena)set_non_main_arena (victim);
    #if USE_TCACHE/* Fill cache first, return to user only if cache fills.We may return one of these chunks later.  */ // 首先用来填充tcache、如果tcache满了的话才返回给用户if (tcache_nb && tcache->counts[tc_idx] < mp_.tcache_count){tcache_put (victim, tc_idx);return_cached = 1; // 标记一下tcache现在 可以返回的chunk 等这个while循环对unsorted bin处理完之后 直接用tcache来返回continue; // 结束这次处理}else{
    #endifcheck_malloced_chunk (av, victim, nb); // 如果没有使用tcache(即在glibc-2.26以前) 那么直接返回刚才移出unsorted bin的chunkvoid *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;
    #if USE_TCACHE}
    #endif}
    

    存在的利用:

    • 只需要能任意修改size值 即可完成overlapping。(off_by_one)

    演示:

    原始的堆分布,这里unsorted bin 中的chunk在main_arena中的last_remainder字段是要有标记的

    image-20240929172247854

    伪造,将size改大,在对应的位置放上适当的prev_size 和 chunksize(过检查):

    image-20240929172755211

    再申请size完全相同的chunk,就能申请到包含chunk0x20的chunk了,造成overlapping:

    这里通过 对unsorted bin中的chunk的 prev_size检查

    image-20240929181024737

    这里通过prev_inuse检查 (上面伪造0xe0 和 0x20的原因就是通过这个检查):

    image-20240929181147401

    将chunk 移出 unsorted bin:

    image-20240929181310257

    检查 chunk的size和 申请的大小是否完全相同:

    image-20240929181347939

    将取出的chunk 标记为已使用,即 相邻的高地址处的chunk的prev_inuse位为1

    image-20240929181518481

    tcache_nb不为0 且 tcache未满,就将取出的chunk放入到对应的tcache中,return_cached置为1(后续直接用tcache来返回):image-20240929182503685

    image-20240929182801131

    随后循环结束,该轮对unsorted bin的处理结束,再从tcache中取出:

    image-20240929183244681

    image-20240929183346710

    最后申请到伪造的chunk ,成功overlapping:

    image-20240929173805423

  5. 当上面的size(unsorted bin 中取出的chunk的size) 和 申请的大小不完全相同时,会将移出的chunk 置入到对应的bin(small 、large bin中):

              /* place chunk in bin */if (in_smallbin_range (size)) // chunk的size 在small bin的范围内{victim_index = smallbin_index (size); // 通过size找到要置入的small bin链 在main_arena.bins数组中的下标bck = bin_at (av, victim_index); // 通过下标 找到对应的 small bin链fwd = bck->fd; // 拿到链首}else // chunk的size 在large bin的范围内{victim_index = largebin_index (size); // 找large bin链 在main_arena.bins数组中的下标bck = bin_at (av, victim_index); // 找到链首fwd = bck->fd;/* maintain large bins in sorted order */ // 要保证large bins中的chunk处于有序的状态下if (fwd != bck) // 找到的 large bin链非空{/* Or with inuse bit to speed comparisons */size |= PREV_INUSE;/* if smaller than smallest, bypass loop below */assert (chunk_main_arena (bck->bk));if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) // 取出的chunk 小于当前large bin中最小的chunk时{fwd = bck;bck = bck->bk; // large bin中最小的chunk// 将取出的chunk链入到 large bin中 (更新fd\bk_nextsize字段)保证large bin处于有序状态victim->fd_nextsize = fwd->fd; // fwd->fd的值是该large bin链上最大的chunk地址 victim是新的最小chunk,最小的chunk的fd_nextsize字段 要指向size最大的chunk地址victim->bk_nextsize = fwd->fd->bk_nextsize; // fwd->fd->bk_nextsize是原链中最小size的chunk,现在排在victim前面fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; // 更新原来最小chunk前后(指size大小)chunk的fd\bk_nextsize}else // size不小于当前large bin中的最小chunk (这部分在glibc-2.31之后新增调表检查(对fd\bk_nextsize检查),所以就不在利用){assert (chunk_main_arena (fwd));while ((unsigned long) size < chunksize_nomask (fwd)){fwd = fwd->fd_nextsize;assert (chunk_main_arena (fwd));}if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd))/* Always insert in the second position.  */fwd = fwd->fd;else{victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;}bck = fwd->bk;}}else // 找到的 large bin链是空的 直接用chunk本身来填充fd\bk_nextsize字段victim->fd_nextsize = victim->bk_nextsize = victim;}// 最后无论是放入small bin 还是 large bin都会进行下面这段处理mark_bin (av, victim_index); // 标记binmap字段 ==> 最后查找bin链时会使用到 ,整个glibc中 只有这个位置能更新binmapvictim->bk = bck; // 插入 : 将取出的chunk置入对应的bin中victim->fd = fwd;fwd->bk = victim; // 更新 对应bin中的链首bck->fd = victim; #define mark_bin(m, i)    ((m)->binmap[idx2block (i)] |= idx2bit (i))
    
  6. 先看置入small bin 的情况:

    先准备一个unsorted bin chunk

    image-20240930095119391

    再任意申请一个chunk,开始拿到unsorted bin中chunk时会进行双向链表检查:

    image-20240930095302539

    这里将拿到的chunk移出unsorted bin:

    image-20240930095927378

    这里开始进入small bin 的处理:

    image-20240930100048108

    在main_arena中找到与chunk size 对应的small bin链:

    image-20240930100226680

    最后在这里将其放入到small bin链中,再继续进行循环:

    image-20240930100406405

    image-20240930100509648

  7. 再看置入large bin的情况(large bin attack 利用):

    image-20240930101329596

    再申请一个chunk,将该unsorted bin中的chunk放入large bin中,这里根据large bin的特性有4种情况来更新fd\bk_nextsize字段,来保证large bin处于有序

    • 找到的large bin是空链
    • unsorted bin中的chunk 与 large bin中存在的chunk大小相同
    • unsorted bin中的chunk size 小于 对应large bin中最小的chunk (高版本上普遍利用)
    • unsorted bin中的chunk size 大于 对应large bin中最小的chunk 且 其中不存在size相同的chunk

    主要看第三种情况,在glibc-2.30开始,对地四种情况的处理上会增加跳表检查,而对于第三种情况则没有检查:

    image-20240930102312982

    先将一个大chunk置入到large bin中,再安排一个相对小的chunk放入unsorted bin中 (两个chunk的size要在同一个large bin链的范围之内),满足第三种情况的条件:

    chunk_0x440 和 chunk_0x460 都在0x440-0x470范围之内

    image-20240930103550201

    再申请一个更大的chunk,来将unsorted bin中的chunk 置入到该large bin中(不损坏上面两个chunk就行):

    unsorted bin 的双向链表检查

    image-20240930103847425

    后续取出chunk,进入到large bin得到处理:

    拿到main_arena.bins数组中 对应large bin 的下标,并拿到对应链的头 bck

    image-20240930104145782

    这里将取出的chunk的size找到的large bin链中的最小chunk的size进行比较:

    image-20240930104343128

    通过比较后进入fd\bk_nextsize赋值,这里存在large bin attack 的利用:

    通过前面的调试,可以看到,直到进入这里都没有对 large bin中本身的chunk(这里的fwd->fd的值) 的fd\bk_nextsize字段进行检查,所以即使修改了上面的值 也不会影响程序

    image-20240930105500412

    这里将fwd->fd->bk_nextsize字段上的值 修改为 t arget_addr ,那么在后面一句赋值的时候:

    victim->bk_nextsize->fd_nextsize = victim
    等于
    target_addr->fd_nextsize = victim
    

    就可以在 target_addr + 0x20 的位置放上victim 即一个堆地址(unsorted bin中拿出的那个chunk地址)

    如果要多次利用large bin attack ==> 只需要每次修改哪个large bin中最大的chunk的bk_nextsize字段的值即可

    最后去更新large bin链的fd\bk字段(这里也没有检查),将chunk链入对应的large bin链:

    image-20240930105753523

    image-20240930105912326image-20240930110710092

    这里可以看到 fwd的bk指向最小的chunk地址fd指向最大的chunk地址

bin处理

  1. 上面结束对unsorted bin的处理后,会进入到对large bin 的处理:

    #define first(b)     ((b)->fd)	  
    #define last(b)      ((b)->bk)/*If a large request, scan through the chunks of current bin insorted order to find smallest that fits.  Use the skip list for this.*/ // 如果申请的大小在large范围 用跳表从小开始 来查找最适合的size大小(large bin中找chunk,满足大小的chunk中,拿size最小的那个chunk)if (!in_smallbin_range (nb)){bin = bin_at (av, idx); // idx 是前面计算出来的large bin在main_arena.bins数组中的下标/* skip scan if empty or largest chunk is too small */ // 如果是空的,或者large bin中的chunk大小不满足 就直接跳过if ((victim = first (bin)) != bin&& (unsigned long) chunksize_nomask (victim) // 最大的chunk的size值>= (unsigned long) (nb)){victim = victim->bk_nextsize; // 找到最小的chunkwhile (((unsigned long) (size = chunksize (victim)) < (unsigned long) (nb)))victim = victim->bk_nextsize; // 从小到大依次比较size,找到最适合的那个size/* Avoid removing the first entry for a size so that the skiplist does not have to be rerouted.  */ // 避免取出跳表的链首chunkif (victim != last (bin) // 检查是否为large bin中最后一个空闲块 (last(bin)找到最末尾的那个chunk) ==> 为了保证后面victim->fd 能取到另外一个chunk 而不是本chunk&& chunksize_nomask (victim) == chunksize_nomask (victim->fd)) // 如果相邻的下一个chunk的size 与找到的chunk大小如果相同victim = victim->fd; // 找到相同size的另外一个chunk(不是跳表的链首),将其拿出来后续进行切割remainder_size = size - nb; // 准备切割unlink_chunk (av, victim); // 解链 移出该large bin链/* Exhaust */if (remainder_size < MINSIZE) // 不满足切割条件(切割后的大小 不能成为一个chunk){set_inuse_bit_at_offset (victim, size); // 直接拿走if (av != &main_arena)set_non_main_arena (victim);}/* Split */ // 可以切割else{remainder = chunk_at_offset (victim, nb);/* We cannot assume the unsorted list is empty and thereforehave to perform a complete insert here.  */bck = unsorted_chunks (av);fwd = bck->fd;if (__glibc_unlikely (fwd->bk != bck)) // unsorted bin双向链表检查malloc_printerr ("malloc(): corrupted unsorted chunks");// 切割后剩下的置入到unsortd bin中 但是这里没有标记main_arena中的last_remainder字段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;}}
    

    这里再重新看一下unlink函数:

    /* Take a chunk off a bin list.  */
    static void unlink_chunk (mstate av, mchunkptr p)
    {if (chunksize (p) != prev_size (next_chunk (p)))malloc_printerr ("corrupted size vs. prev_size");mchunkptr fd = p->fd;mchunkptr bk = p->bk;if (__builtin_expect (fd->bk != p || bk->fd != p, 0)) // 双向链表检查malloc_printerr ("corrupted double-linked list");fd->bk = bk;bk->fd = fd;
    // 这里已经移出了 large bin 但是后续还要处理fd\bk_nextsize 来保证large bin的有序性// 对large bin中chunk的fd\bk_nextsize字段的处理if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL) // 保证在large bin的范围内 并且 p->fd_nextsize不为空(即是跳表的链首才往下处理,如果不是就不用处理,因为不是跳表的链首的chunk 其fd\bk_nextsize字段都是空的){if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p) // 跳表的双向链表检察malloc_printerr ("corrupted double-linked list (not small)");if (fd->fd_nextsize == NULL) // 如果fd 不是相邻的调表的链首{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;}}
    }
    

    这里调试看一下对large bin的切割处理:

    后续申请的chunk要在该large bin的size范围之内 (这里可以看出,如果不是跳表的链首,则fd\bk_nextsize字段为空)

    image-20240930162741240

在这里插入图片描述

申请0x400的chunk,刚好在切割掉最后的chunk_0x460后还能剩下0x20,从而保证一个chunk:

这里开始进入对large bin的处理

image-20240930163614549

通过前面用 idx = largebin_index (nb) 取得的下标idx, 拿到了该large bin链 ,这里检查该链是否为空、其中的chunk的size是否满足申请的大小:

image-20240930164028268

这里拿到了large bin链中size最小的chunk,开始从小到大寻找适合的chunk:

image-20240930164322935

这里找到的最小chunk的size满足申请大小,所以直接退出循环:

image-20240930164431285

这里由于找到的chunk 是large bin链中的最末尾的chunk,所以肯定不存在后继的空闲块,所以只能使用跳表的链首来解链(这里如果不是large bin链的最末尾的chunk,再判断该chunk的后继的空闲块size与其是否相同,从而决定是否更新):

image-20240930174552895

unlink中对large bin进行双向链表检查,随后从large bin中取出:

image-20240930165613549

然后处理large bin中的fd\bk_nextsize字段,其中 p->fd_nextsize != NULL 保证 解链的是跳表的链首(不是链首不需要处理fd\bk_nextsize字段):

image-20240930165910461

跳表的完整性检查:

image-20240930170144255

这里没有通过判断 所以进入else 处理:

image-20240930172207506

最后直接更新跳表前后的 fd\bk_nextsize这字段:

image-20240930172502339

  1. 再看一下 如果解链的chunk 不是跳表的链首:

    image-20240930173101584

    再申请 chunk_0x440 :

    这里经过判断后 确认victim 即跳表的链首 不是该large bin链的最末尾的chunk,即存在后继空闲块 ,且后续判断size与其相同,则更新待解链的chunk 为victim的后继空闲块(size相同) ,cmove指令是一个条件赋值指令条件:ZF=1 => 即相等,才赋值:

    image-20240930175337890

    进入unlink,这里由于取的不是 跳表的链首 所以 p->fd_nextsize != NULL 没通过,即不处理后续large bin的fd\bk_nextsize字段直接ret(也能保证large bin有序):

    image-20240930175842211

  2. 最后进入切割:

    比较切割后的大小 是否 保证能达到最小chunk

    image-20240930182037230

    切割后将剩余的chunk放入到unsorted bin中:

    image-20240930182154214

    image-20240930182711189

  3. 如果没有进入到上面的large bin中,或者进入了但是对应的large bin链中没有满足申请大小的chunk,就会进入下面这段,在small bin 和 large bin中依次从小到大查找适合的bin链:

          /*Search for a chunk by scanning bins, starting with next largestbin. This search is strictly by best-fit; i.e., the smallest(with ties going to approximately the least recently used) chunkthat fits is selected.The bitmap avoids needing to check that most blocks are nonempty.The particular case of skipping all bins during warm-up phaseswhen no chunks have been returned yet is faster than it might look.*/ // 通过查看位图,分配器可以快速确定哪些块是空闲的,而不需要实际检查内存块本身。这种技术减少了分配器必须执行的检查次数,因为如果位图指示一个块是非空的(即已被占用),分配器就不必检查该块的实际内容。++idx; // 从恰好大一点的 bin链开始搜索(因为前面已经按照idx 来寻找了符合条件的chunk,所以这里就可以直接跳过自身大小的bin,从下一个大小的bin开始搜索)bin = bin_at (av, idx);block = idx2block (idx); // 将bins中的下标 转化成 binmap中的下标 ==> 先大致确定范围map = av->binmap[block]; // 使用了binmap,以1bit来标记不为空的 bin链,来实现快速查找bit = idx2bit (idx); // bit的作用与map类似,但是idx转化过来的bit值是假定该bin链上有剩余chunk时的binmap中的值 for (;; ) // 这里循环的作用是为了 更新binmap数组 使之更贴近真实的bin链(后面进入if (victim == bin) 后会开始下一次循环){/* Skip rest of block if there are no more set bits in this block.  */if (bit > map || bit == 0) // 如果按照下标转化过来的bit 比 对应真实的map要大,说明map 中标记的有空闲的bin 不满足申请条件(map标记的bin链中的chunk的size太小) 则可以直接跳过这个binmap,去一个{do{if (++block >= BINMAPSIZE) /* out of bins */// 超出了binmap数组的范围,就直接用topgoto use_top;}while ((map = av->binmap[block]) == 0); // 直到取出的map不是0bin = bin_at (av, (block << BINMAPSHIFT)); // 从当前map能标记的最小bin链开始bit = 1;}/* Advance to bin with set bit. There must be one. */while ((bit & map) == 0) // 从小到大找到map标记的bin链 即从右往左map非0的那个bit位{bin = next_bin (bin);bit <<= 1;assert (bit != 0);}/* Inspect the bin. It is likely to be non-empty */victim = last (bin); // 找到了适合的最小bin链(size最小)/*  If a false alarm (empty bin), clear the bit. */ // 如果找到的bin链是空的,就更新binmap标志,将其标志为空(这里是因为binmap的标记与真实的bin可能不符合,因为后面再切割完成之后没有更新这个标志,或者是前面对于small bin的申请完成之后也没有更新这个标志)if (victim == bin) {av->binmap[block] = map &= ~bit; /* Write through */bin = next_bin (bin);bit <<= 1;}else // 找到的bin链不是空的,就可以开始切割了{size = chunksize (victim);/*  We know the first chunk in this bin is big enough to use. */assert ((unsigned long) (size) >= (unsigned long) (nb));remainder_size = size - nb;/* unlink */unlink_chunk (av, victim); // 将链首解链出来/* Exhaust */if (remainder_size < MINSIZE) // 切割后是否能保持最小的chunk,不行就不用切割{set_inuse_bit_at_offset (victim, size);if (av != &main_arena)set_non_main_arena (victim);}/* Split */else // 切割{remainder = chunk_at_offset (victim, nb);/* We cannot assume the unsorted list is empty and thereforehave to perform a complete insert here.  */bck = unsorted_chunks (av);fwd = bck->fd;if (__glibc_unlikely (fwd->bk != bck)) // 剩下的置入到unsorted bin中malloc_printerr ("malloc(): corrupted unsorted chunks 2");remainder->bk = bck;remainder->fd = fwd;bck->fd = remainder;fwd->bk = remainder;/* advertise as last remainder */ if (in_smallbin_range (nb))// 标志一些 该chunk是切割后剩余的chunk,后续可以在处理unsorted bin时第一步直接用来切割,只有申请大小的small bin的范围内才会被标记last_remainderav->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;}}
    

    首先看一下,main_arena中bins数组binmap的结构

    • bin数组中前两个元素为unsorted (2),后面的都是 small bin(62*2) 和 large bin(63 *2)。一共125个bin链
    • binmap中有4个元素,每一个为无符号整型 ==> 4字节 ==> 4*32= 128 位 (能标志128个bin链) ,可以标识所有的samll bin 和 large bin链 ==> 将125个bin链 分成4组,每一组里面都有1bit来标记一个bin 链

    image-20241002100147573

    这里调试看一下,使用binmap寻找适合的bin链的过程,(binmap数组是在对unsorted bin处理,将chunk置入到small bin和large bin时进行维护的):

  4. 维护过程:

    先准备一个属于small bin 的chunk在unsorted bin中

    image-20241002110118204

    再置入到small bin中:

    image-20241002110358049

    开始更新binmap:

    image-20241002110648767

    image-20241002110751031

    更新完成,大小位0xb0的smnall bin就会被binmap标记 表示该bin链不为空

    image-20241002110822691

    如果将0x20的chunk放入到small bin中,binmap会不会用最小的那个bit位来标记这个bin链:

    image-20241002111543221

    最小的chunk_0x20,是用0b100 即第3位来标记的 ,后面的chunk依次类推:

    image-20241002111957584

  5. 看一下寻找bin链的过程,将chunk_0xb0置入到small bin中,再申请chunk_0x20:

    small bin中的chunk成功被binmap标记 (这里不放入到small bin中也可以,因为没有被last_remainder标记,所以开始处理unsorted bin时不会被切割,还是会被先放入到small bin中)

    image-20241002122550393

    再申请0x20的chunk:

    这里bin跳过0x20,直接检查0x30的bin 链,被标记再bimmap[0]中,所以从binmap[0]中拿map,bit ==> 0x8 =>0b1000,说明chunk_0x30在map中的标记位是第4位

    image-20241002123018056

    这里bit比map小,说明map标记的bin链中,有满足申请大小的空闲chunk

    image-20241002123257079

    这里开始逐位寻找被标记的bin链(从小到大),bit左移1位,将相当于寻找的chunk的size大0x10:

    image-20241002123433274

    最后找到了被map标记的bin链,代表0xd0大小的空闲块:

    image-20241002123712334

    检查找到的bin链是否为空:

    image-20241002123858725

    最后unlink从bin链中取出 chunk,开始切割:

    image-20241002124049541

    切割后剩下的chunk,放入unsorted bin中:

    image-20241002124149692

    如果申请的大小在small bin范围内,还要标记一下main_arena中的last_remainder字段:

    在这里插入图片描述

    完成申请,剩余的chunk:

    image-20241002124505623

top

  1. 当上面的情况都无法满足时,会启用top 直接从top chunk中拿堆:
    use_top:/*If large enough, split off the chunk bordering the end of memory(held in av->top). Note that this is in accord with the best-fitsearch rule.  In effect, av->top is treated as larger (and thusless well fitting) than any other available chunk since it canbe extended to be as large as necessary (up to systemlimitations).We require that av->top always exists (i.e., has size >=MINSIZE) after initialization, so if it would otherwise beexhausted by current request, it is replenished. (The mainreason for ensuring it exists is that we may need MINSIZE spaceto put in fenceposts in sysmalloc.)*/victim = av->top; // 从main_arena中拿到top chunk地址size = chunksize (victim); // 拿到top chunk的sizeif (__glibc_unlikely (size > av->system_mem)) // 限制了top chunk的最大size值 0x21000(这个检查是glibc-2.29新增的)malloc_printerr ("malloc(): corrupted top size");if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) // top chunk的size满足,切割后还能剩下一个最小的chunk_0x20{remainder_size = size - nb; // 剩余chunk的大小remainder = chunk_at_offset (victim, nb); // 剩余chunk的地址av->top = remainder; // 更新top chunk地址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 gethere for all block sizes.  */else if (atomic_load_relaxed (&av->have_fastchunks)) // have_fastchunks中标记了fastbin中有没有剩余chunk {malloc_consolidate (av); // 将fastbin中的chunk转移 (当top chunk的size不足且fastbin中有chunk时,会调用malloc_consolidate)/* restore original bin index */if (in_smallbin_range (nb)) // 重新计算申请的chunk的size在small bin\large bin中对应的idx(前面使用binmap寻找时 idx被修改过了)idx = smallbin_index (nb);elseidx = largebin_index (nb);}/*Otherwise, relay to handle system-dependent cases*/else{void *p = sysmalloc (nb, av); // free掉原来的top chunk进入unsorted bin,再将其扩充if (p != NULL)alloc_perturb (p, bytes);return p;}}
}

利用1:

  1. 在glibc-2.29之前 没有新增这个检查之前 __glibc_unlikely (size > av->system_mem) ,通过将top chunk的size改大,存在一个house of force的利用,可以将top chunk延申到任意位置,从而任意地址申请chunk:

  2. 用glibc-2.25演示:

    将top chunk的size改大,并计算出,top chunk到 目标地址的距离,这里我申请到_IO_list_all:

    image-20241002185126301

    往高地址申请chunk,申请的大小就是刚才计算出来的值:

    这里从main_arena中,取出原本的top chunk地址,并用申请的size和原先的top chunk的size比较

    image-20241002185214500

    通过检查后计算新的top chunk地址,并将其放入到main_arena的top字段中:

    image-20241002185542133

    在glibc-2.29之后,house of force就不适用了:

    可以看到这里由于伪造的top chunk的size比main_arena中的system_mem限制的要大,所以会报错退出,所以不是适用:

    image-20241002185838409

利用2: 当空间不足时,会适用syamalloc进行扩容

  1. 如果top chunk的size 不能满足申请的大小,且fastbin中没有空闲块,即进入到最后的else中:

    这里直接把top chunk的size改小:

    image-20241002204908792

    再申请一个大于top chunk sizede 堆,在use_top 这个标签之前,对top chunk的size没有任何检查的:

    我们是将top chunk的size该小,所以这里的system_mem检查能通过

    image-20241002205258475

    这里由于申请的大小大于top chunk剩余的size,所以不会进入切割,后面fastbin中没有空闲块所以else if也不会进入:

    image-20241002205340532

    这里最后调用sysmalloc函数,传入的参数是申请的大小 和 main_arena地址,接收一个指针p作为返回值:

    image-20241002205641053

    进入sysmalloc函数:

    这里如果申请的大小 nb > 0x2000(mmap的阈值)并且 mmap分配的堆的数量n_mmap 要小于 最大值n_mmap_max,那么就会以mmap的形式进行扩容

    image-20241003165140909

    最后扩容出来的效果,和使用main_arena管理的堆不在一个段上面:

    image-20241003165916179

    image-20241003165933011

  2. 这里看另外一种,对主内存区的分配中使用brk方式扩容:

    这里检查了top chunk,根据old_top的地址(main_arena中取出) 和 old_size 计算出top chunk的顶地址(即将top chunk申请完时的地址)。

    image-20241002210148947

    old_size至少要大于最小的chunkprev_inuse位必须为1old_end & (pagesize - 1) ==> 要保证top按0x1000页对齐

    image-20241002210816799

    这里再次检查,top chunk的size是否真的不够申请,如果条件为假,则报错退出:

    image-20241002211038545

    这里判断是不是主分配区扩容,如果是主分配区,则直接扩容Top chunk

    在这里插入图片描述

    扩容的size 等于 nb(此次分配的容量) + top_pad(每次分配扩展值128K = 0x020000)+ MINSIZE对齐字节,最后得出size大小。是连续性分配的(contiguous (av)),可以减去老的Top chunk剩余的old_size值,:

    image-20241003172406928

    然后使用系统调用(sbrk)分配size大小的内存,可以看到在原有的堆的基础上又增加了0x21000的空间,而且和之前的空间时连续的(这里如果分配失败的话,会从新计算size大小,并采用mmap的方式分配内存):

    image-20241003172948066

    image-20241003173127874

    调整main_arena中系统内存大小(整个top chunk的大小):

    image-20241003173821231

    如果通过brk扩容的空间是连续的(想对上次的top chunk顶地址),则直接更新原来top chunk的size即可。但是我们修改了top chunk的size,所以导致了计算出来的old_end 和 新扩展的堆的起始地址brk不相等,即地址不连续:

    image-20241003174138715

    这里进行第二次扩容,correction = brk前置的对齐字节 + 老的top的size + 新的brk尾部的对齐字节,可以看到从新增加 了0x1000大小。如果这里不阔容,而是直接使用第一次扩容的起始地址,可能:

    image-20241003175440813

    image-20241003180041047

    第二次扩容完成后,调整top chunk,top指向调整过的aligned_brk地址,后续释放掉原来的top chunk:

    image-20241003180228949

    这里,减小top chunk的size来释放,是为了在新的top和原来的top 之间插入一个栅栏,保证原来的top chunk能被正常释放 (不会报错),:

    image-20241003181703535

    设置栅栏,这么设置的原有和free函数中的实现有关(为了防止原来的top chunk向前合并(向高地址)),导致free top时出错,这里在这篇文章中有解释 伪造unsortedbin释放时 top chunk的衔接问题 ,这里采用的就是类似的方法来防止伪造的fake_chunk向高地址合并:

    image-20241003181837252

    最后释放掉原来的top chunk。进入到unsorted bin中:

    image-20241003182209753

    image-20241003182250237

    最后,直接从新的top 中切割出了我们申请的chunk(和前面切割top的操作一样):

    image-20241003182528897

  3. 最后在pwngdb中使用vis 命令时,不会显示新的top,新的top要在main_arena中查看:

    image-20241003183151259

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/438996.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PHP陪玩系统源码】游戏陪玩系统app,陪玩小程序优势

陪玩系统开发运营级别陪玩成品搭建 支持二开源码交付&#xff0c;游戏开黑陪玩系统: 多客陪玩系统&#xff0c;游戏开黑陪玩&#xff0c;线下搭子&#xff0c;开黑陪玩系统 前端uniapp后端php&#xff0c;数据库MySQL 1、长时间的陪玩APP源码开发经验&#xff0c;始终坚持从客户…

CentOS 替换 yum源 经验分享

视频教程在bilibili:CentOS 替换 yum源 经验分享_哔哩哔哩_bilibili问题原因 解决方法 1. 进入镜像目录 [rootlocalhost ~]# cd /etc/yum.repos.d/ 2.备份文件 [rootlocalhost yum.repos.d]# rename repo bak * 3.寻找阿里镜像源复制 https://developer.aliyun.com/mirror/ …

【pytorch】张量求导4

再再接上文&#xff0c;看到作者有一个关于向量乘矩阵的描述。 经过搜索发现&#xff0c;现在的pytorch已经修复了这一问题&#xff0c;提供了mv()和matmul()两种方式实现矩阵和一维向量的乘积&#xff0c;可以参看这篇文章。 经过查阅pytorch的文件&#xff0c;找到了cuda侧…

【重学 MySQL】五十三、MySQL数据类型概述和字符集设置

【重学 MySQL】五十三、MySQL数据类型概述和字符集设置 MySQL数据类型概述MySQL字符集设置注意事项 MySQL数据类型概述 MySQL是一个流行的关系型数据库管理系统&#xff0c;它支持多种数据类型&#xff0c;以满足不同数据处理和存储的需求。理解并正确使用这些数据类型对于提高…

SpringBoot整合异步任务执行

同步任务&#xff1a; 同步任务是在单线程中按顺序执行&#xff0c;每次只有一个任务在执行&#xff0c;不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务&#xff0c;无法同时处理多个任务&#xff0c;响应慢&#xff0c;影响…

linux中缓存,在kafka上应用总结

linux中的缓存 页缓存 pagecatch&#xff08;读缓存用于提供快速读&#xff09;块缓存&#xff08;用于提供其他设备快速写&#xff09;当对读缓存读的时候&#xff0c;修改了读的数据&#xff0c;页缓存就会被标记为脏数据&#xff0c;等到写的时候它会向块缓存同步数据&…

Azure DevOps Server:不能指派新增的用户

Contents 1. 概述2. 解决方案 1. 概述 近期和微软Azure DevOps项目组解决了一个“无法指派开发人员”的问题&#xff0c;在此分享给大家。问题描述&#xff1a; 在一个数据量比较大的Azure DevOps Server的部署环境中&#xff0c;用户发现将新用户的AD域账户添加到Azure DevOps…

搭建shopify本地开发环境

虽然shopify提供了在线编辑器的功能&#xff0c;但是远不及本地编辑器方便高效&#xff0c;这篇文章主要介绍如何在本地搭建shopify开发环境&#xff1a; 1、安装nodejs 18.2 2、安装git 3、安装shopify cli ,使用指令: npm install -g shopify/clilatest 4、安装ruby 5、…

【MAUI】CollectionView之 垂直网格

App主页或者导航页面中动态按钮的垂直网格布局 在 XAML 中,CollectionView 可以通过将其 ItemsLayout 属性设置为 VerticalGrid,在垂直网格中显示其项: <CollectionView ItemsSource="{Binding Monkeys}"ItemsLayout

TS(type,属性修饰符,抽象类,interface)一次性全部总结

目录 1.type 1.基本用法 2.联合类型 3.交叉类型 2.属性修饰符 1.public 属性修饰符 属性的简写形式 2.proteced 属性修饰符 3.private 属性修饰符 4.readonly 属性修饰符 3.抽象类 4.interface 1.定义类结构 2.定义对象结构 3.定义函数结构 4.接口之间的继…

C语言 动态数据结构的C语言实现内存映像

C程序的内存映像 C程序中变量的内存分配方式  C程序中变量的内存分配方式  从静态存储区分配  全局变量和静态变量 C程序中变量的内存分配方式  从静态存储区分配  全局变量和静态变量  在栈上分配  存放函数参数值&#xff0c;局部变量值等  …

已解决:AttributeError: ‘str‘ object has no attribute ‘decode‘

已解决&#xff1a;AttributeError: ‘str’ object has no attribute ‘decode’ 文章目录 写在前面问题描述报错原因分析 解决思路解决办法1. 确保只对 bytes 对象调用 decode()2. 将 Python 2 的旧代码迁移到 Python 33. 检查数据来源4. 处理编码不一致的问题5. 使用 six 库…

pygame--超级马里奥(万字详细版)

超级马里奥点我下载https://github.com/marblexu/PythonSuperMario 1.游戏介绍 小时候的经典游戏&#xff0c;代码参考了github上的项目Mario-Level-1&#xff0c;使用pygame来实现&#xff0c;从中学习到了横版过关游戏实现中的一些处理方法。原项目实现了超级玛丽的第一个小…

利用vue-capper封装一个可以函数式调用图片裁剪组件

1. 效果 const cropData await wqCrop({prop:{img,autoCrop: true, // 是否开启截图框maxImgSize: 600,autoCropWidth: 30,canMove: true, // 图片是否可移动canMoveBox: true, // 截图框是否可移动fixedBox: false, // 截图框是否固定}});console.log(cropData);使用wqCrop会…

virtualbox配置为NAT模式后物理机和虚拟机互通

virtualbox配置为 NAT模式后&#xff0c;虚拟机分配到的 IP地址一般是 10.xx网段的&#xff0c;虚拟机可以通过网络地址转换访问物理机所在的网络&#xff0c;但若不做任何配置&#xff0c;则物理机无法直接访问虚拟机。 virtualbox在提供 NAT配置模式时&#xff0c;也提供了端…

重置linux后vscode无法再次使用ssh连接

如果你使用过vscode ssh远程连接了一个Linux系统&#xff0c;但该系统被重置了&#xff0c;并且关键配置没有改变。再次使用vscode连接时&#xff0c;vscode可能无法连接。 原因&#xff1a;vscode远程连接后会在C:\Users{{你的用户名}}.ssh下的known_hosts和known_hosts.old。…

osg 矩阵相关

下面结果是一样的 osg::Matrix mtrixx;mtrixx.makeRotate(90 / 180.f * osg::PI, osg::Vec3(1, 0, 0));osg::Matrix mtrixx12 osg::Matrix::rotate(90 / 180.f * osg::PI, 1, 0, 0); 下面结果是一样 osg::Matrix m1;m1.makeTranslate(osg::Vec3(18, 12,3));osg::Matrix m2 os…

QT系统学习篇(2)- Qt跨平台GUI原理机制

一、Qt工程管理 1、新建项目&#xff1a; 我们程序员新建项目对话框所有5类项目模板 Application: Qt的应用程序&#xff0c;包含Qt Quick和普通窗口程序。 Library: 它可以创建动态库、静态库、Qt Creator自身插件、Qt Quick扩展插件。 其他项目: 创建单元测试项目、子目录项…

hackmyvm-Nubula靶机

主机发现 sudo arp-scan -l 以sudo超级管理员权限运行arp-scan 扫描整个局域网 -l扫描本地网络的所有网段 发现靶机ip&#xff1a;192.168.91.208 nmap对靶机进行端口扫描发现22和80端口 22&#xff1a;进行ssh远程登录的开放端口 80&#xff1a;超文本传输协议的web服务…

【leetcode】 45.跳跃游戏 ||

如果我们「贪心」地进行正向查找&#xff0c;每次找到可到达的最远位置&#xff0c;就可以在线性时间内得到最少的跳跃次数。 例如&#xff0c;对于数组 [2,3,1,2,4,2,3]&#xff0c;初始位置是下标 0&#xff0c;从下标 0 出发&#xff0c;最远可到达下标 2。下标 0 可到达的…