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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章