glibc-2.29学习(上)

堆利用在CTF的PWN题目中一直占比较大,而最近的比赛的平台也逐渐从Ubuntu16,Ubuntu18向Ubuntu19甚至更高版本转移,为此学习一下libc-2.29中新的特性对做题还是很有帮助的


libc-2.29新变化

libc-2.29中修改了对tcache_entry的定义,通过注释我们也能看出新增加的key是为了检测double free的,如下

/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key;
} tcache_entry;

在tcache_put中,新增了 e->key = tcache;

static __thread tcache_perthread_struct *tcache = NULL;
······
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);

  /* Mark this chunk as "in the tcache" so the test in _int_free will
     detect a double free.  */
  e->key = tcache;//tcache为tcache_perthread_struct的地址,这里用该地址作为key

  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

同时在free中也多了对double free的检测

if (tcache != NULL && tc_idx < mp_.tcache_bins)
      {
	/* Check to see if it's already in the tcache.  */
	tcache_entry *e = (tcache_entry *) chunk2mem (p);

	/* This test succeeds on double free.  However, we don't 100%
	   trust it (it also matches random payload data at a 1 in
	   2^<size_t> chance), so verify it's not an unlikely
	   coincidence before aborting.  */
	if (__glibc_unlikely (e->key == tcache))//当key和tcache相等,才会检查是否有double free的可能(有时候可能这里的值正好和tcache相同)
	  {
	    tcache_entry *tmp;
	    LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
	    for (tmp = tcache->entries[tc_idx];
		 tmp;
		 tmp = tmp->next)//这个循环检测该chunk是否已经进入tcache
	      if (tmp == e)
		malloc_printerr ("free(): double free detected in tcache 2");
	    /* If we get here, it was a coincidence.  We've wasted a
	       few cycles, but don't abort.  */
	  }

	if (tcache->counts[tc_idx] < mp_.tcache_count)//tcache没有被装满
	  {
	    tcache_put (p, tc_idx);
	    return;
	  }
      }

绕过策略

在free函数中,只有当key和tcache相等时才会去判断是否double free(所以并不是每一个free的chunk都会被检查是否是double free),而如果程序中正好有漏洞可以把key改了(包括但不限于UAF),就可以直接跳过检查了

libc-2.29中出现的利用方法

libc-2.29出现了一种叫stash的机制,基本原理就是当调用_int_malloc时,如果从smallbin或者fastbin中取出chunk之后,对应大小的tcache没有满,就会把剩下的bin放入tcache中,代码如下:
fastbin

#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)//检查tcache是否满了
		{
		  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)
		    {
		      if (SINGLE_THREAD_P)
			*fb = tc_victim->fd;
		      else
			{
			  REMOVE_FB (fb, pp, tc_victim);
			  if (__glibc_unlikely (tc_victim == NULL))
			    break;
			}
		      tcache_put (tc_victim, tc_idx);
		    }
		}
#endif

smallbin

bin = bin_at (av, idx);
······
#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)//检查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)
		{
		  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;
		      bck->fd = bin;

		      tcache_put (tc_victim, tc_idx);
	            }
		}
	    }
#endif

Tcache stash unlink attack

在上面的源代码中,可以发现当smallbin剩余部分被放入tcache中,并没有进行检查,而在把smallbin取出给用户使用的时候,是有检查的,如下:

	  bin = bin_at (av, idx);
      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;

在把剩余smallbin放入tcache的时候,关键操作如下

bin = bin_at (av, idx);
······
#if USE_TCACHE
······
			while (tcache->counts[tc_idx] < mp_.tcache_count
		     	&& (tc_victim = last (bin)) != bin)
		  {
		  	if (tc_victim != 0)
		   	  {
		   	  	bck = tc_victim->bk;
		   	  	······
		   	  	bin->bk = bck;
		      	bck->fd = bin;

		      tcache_put (tc_victim, tc_idx);
	            }
		}
	    }
#endif

我们把2个bin放入smallbin(先free的记为smallbin1),6个bin放入对应大小的tcache,如果我们能在不修改smallbin2的fd的情况(为了过第一个检查)下修改其bk,把bk修改为目标地址-0x10,就能在目标地址的位置写入一个不可控的大数(上面代码中的bin)。放入之后tcache数目变为7,就会结束循环

Tcache stash unlink attack+

该操作关键代码如下:

bin = bin_at (av, idx);
······
#if USE_TCACHE
······
			while (tcache->counts[tc_idx] < mp_.tcache_count
		     	&& (tc_victim = last (bin)) != bin)
		  {
		  	if (tc_victim != 0)
		   	  {
		   	  	bck = tc_victim->bk;
		   	  	······
		   	  	bin->bk = bck;
		      	bck->fd = bin;

		      tcache_put (tc_victim, tc_idx);
	            }
		}
	    }
#endif

如果我们把2个bin放入smallbin(先free的记为smallbin1),5个bin放入对应大小的tcache,如果我们能在不修改smallbin2的fd的情况(为了过第一个检查)下修改其bk,把bk修改为目标地址-0x10,当smallbin1被分配给用户,smallbin2进入tcache之后,smallbin的bk就是目标地址-0x10,此时tcache数目为6,循环不结束。新的循环中,tc_victim就是目标地址-0x10,为了让bck->fd = bin;能正常运行,我们需要目标地址-0x10+0x18=目标地址+8指向一处可写的内存

Tcache stash unlink attack++

该操作和Tcache stash unlink attack+基本相同。这次我们把smallbin2的bk部分改为目标地址1-0x10,同时在目标地址1+8处写入目标地址2-0x10的地址,这样就能让目标地址1进入tcache,并且在目标地址2处写入一个不可控的大数(bin)

猜你喜欢

转载自blog.csdn.net/weixin_44145820/article/details/106397981