fastbin_dup_consolidate

how2heap fastbin_dup_consolidate

请注意,本文编写于 129 天前,最后修改于 118 天前,其中某些信息可能已经过时。

0x01 简介

同样是double free,这次利用了 malloc_consolidate() 函数

0x02 源码分析

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main() {
  // malloc 两个相同大小为 0x40 的空间
  void* p1 = malloc(0x40);
  void* p2 = malloc(0x40);
  fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);
  // free p1
  fprintf(stderr, "Now free p1!\n");
  free(p1);
    
  //分配一个large bin来触发malloc_consolidate函数
  void* p3 = malloc(0x400);
  fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
    
  //通过malloc_consolidate函数我们可以把free掉的p1移动到unsorted bin中
  fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");
  free(p1);
    
  //然后就可以触发double free漏洞了
  fprintf(stderr, "Trigger the double free vulnerability!\n");
  fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
  //现在p1既在unsorted bin中又在fastbin中,因此我们再分配两次p1大小的内存,就可以分配到同一款内存
  fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));
}

0x03 调试

1. 第一次free(p1) 后

pwndbg> heap
0x602000 FASTBIN {
  prev_size = 0, 
  size = 81, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x602050 FASTBIN {
  prev_size = 0, 
  size = 81, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x6020a0 PREV_INUSE {
  prev_size = 0, 
  size = 135009, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0 
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

p1 地址 0x602000,free以后被放到 fastbins中

2.  void* p3 = malloc(0x400); 

malloc_consolidate() 函数是定义在 malloc.c 中的一个函数,用于将 fastbin 中的空闲 chunk 合并整理到 unsorted_bin 中以及进行初始化堆的工作,在 malloc() 以及 free() 中均有可能调用 malloc_consolidate() 函数

在p3 malloc 中就调用了malloc_consolidate() 函数

pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x50: 0x602000 —▸ 0x7ffff7dd1bb8 (main_arena+152) ◂— 0x602000
largebins
empty

p1 被放到 smallbins 中

关于 malloc_consolidate 函数

   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);
  /*
    Remove each chunk from fast bin and consolidate it, placing it
    then in unsorted bin. Among other reasons for doing this,
    placing in unsorted bin avoids needing to calculate actual bins
    until malloc is sure that chunks aren't immediately going to be
    reused anyway.
    从 fast bin 中移除每一个chunk,并且合并,然后将它放在 unsorted bin。其中包含其他的原因,放置在
    unsorted bin 不需要计算实际的bins,除非malloc 确定chunks 不会马上被重用。
  */
  maxfb = &fastbin (av, NFASTBINS - 1);
  fb = &fastbin (av, 0);
  do {
    p = atomic_exchange_acq (fb, NULL);
    if (p != 0) {
      do {
        {
          unsigned int idx = fastbin_index (chunksize (p));
          if ((&fastbin (av, idx)) != fb)
            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);
        nextsize = chunksize(nextchunk);
        if (!prev_inuse(p)) {
          prevsize = prev_size (p);
          size += prevsize;
          p = chunk_at_offset(p, -((long) prevsize));
          if (__glibc_unlikely (chunksize(p) != prevsize))
            malloc_printerr ("corrupted size vs. prev_size in fastbins");
          unlink_chunk (av, p);
        }
        if (nextchunk != av->top) {
          nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
          if (!nextinuse) {
            size += nextsize;
            unlink_chunk (av, nextchunk);
          } 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 {
          size += nextsize;
          set_head(p, size | PREV_INUSE);
          av->top = p;
        }
      } while ( (p = nextp) != 0);
    }
  } while (fb++ != maxfb);
}

先确定堆是否被初始化了(也就是get_max_fast()函数),如果没有就初始化堆,然后退出函数
从 fastbin 中获取一个空闲 chunk,尝试向后合并
如果不能向后合并就尝试向前合并
如果向前合并的时候与top_chunk相邻,就直接归到top_chunk中
如果并不相邻就插入到unsorted bin,然后继续取fastbin chunk直到fastbin list为空结束

Debug的时候 为什么不在unsorted呢:

     我们在分配largebin时,ptmalloc会先遍历一下fastbin,将相邻的 chunk 进行合并,并链接到 unsorted bin 中然后遍历 unsorted bin 中的 chunk,如果 unsorted bin 只 有一个 chunk,并且这个 chunk 在上次分配时被使用过,并且所需分配的 chunk 大 小属于 small bins,并且 chunk 的大小大于等于需要分配的大小,这种情况下就直接将该 chunk 进行切割,分配结束,否则将根据 chunk 的空间大小将其放入 small bins 或是 large bins 中

再次力推华庭前辈的《Glibc 内存管理》- Ptmalloc2 源代码分析 ,写的很好,简单易懂又严谨

这就是为什么不在unsorted bin而在small bin中的原因了

3. 第二次 free(p1)后

pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x50 [corrupted]
FD: 0x602000 ◂— 0x0
BK: 0x602000 —▸ 0x7ffff7dd1bb8 (main_arena+152) ◂— 0x602000
largebins
empty

可以看到 p1 既在 fast bins 中,也在 small bins 中

因为再free的时候ptmalloc会发现fastbin是空的,因此就把他扔到fastbin中去了

此时就可以分配两次p1了,一次系统会在fastbin中取出(优先查看fastbin),第二次就会在smallbins中取出 : )

0x04 结论

程序先malloc了两个fastbin chunk(0x40),然后free掉第一个chunk

这里为啥不free第二个呢,因为如果free第二个的话就会和top chunk相邻了,此时会触发top chunk的合并

之后程序调用了malloc函数malloc了一个large bin,触发了malloc_consoldate()函数,导致我们free掉的chunk1被放入了small bin中

然后程序第二次free chunk1,ptmalloc会先看fastbin中有没有,发现没有,于是就把chunk1放到fast bin中了,这时chunk1就在fastbin和smallbin中各有一个

此时程序再申请两次0x40的chunk,ptmalloc先从fastbin中把chunk1取出来给用户,然后再从smallbin中再次把chunk1取出来给用户

我们就有了两个拥有同样内存的chunk

添加新评论

请不要水评论

评论列表