[阅读型]glibc-2.31中的tcache stashing unlink与large bin attack

前言

有幸和校队一起参加21年5月的xctf-final。笔者爆肝连续战斗十小时,在与队友各种讨论下艰难做出了house of pig这一题。事后看出题人的wp,方法不尽相同但也是收获良多,特此记录。

笔者的这题解法是没有large bin attack的(知识盲区),利用tcache stashing unlinkglobal_max_fast,使用fast bin attack申请到_rtld_global附近的内存,劫持exit_hook为one_gadget。其中_rtld_global的地址偏移是个巨坑。

题目的漏洞与功能

这里为了方便阅读,我简化了一个c语言版本。程序可以UAF,可以泄露libc与堆地址。问题在于没有malloc()只有calloc(),并且其大小受到限制。libc版本2.31。

注: calloc()是不走tcache的。无论tcache有无chunk,都会走到_int_malloc()中。

char* ptrs [100] = {0}; int sizes[100] = {0}; int freeable[100] = {0};
int main(){
	int choice = 0, index=0, size=0;
	while(1){
		puts("menu"); scanf("%d", &choice);
		switch(choice){
			case 1: // malloc no out_of_bound
				scanf("%d%d", &index, &size);
				if(index>=0 && index<100 && size>=0x90 && size<=0x450){
					ptrs[index] = (char*)calloc(1, size);
					sizes[index] = size;
					freeable[index] = 1;
					read(0, ptrs[index], size-1);
					ptrs[index][size-1] = 0;
				}
				break;
			case 2: //edit use_after_free, no out_of_bound
				scanf("%d%d", &index, &size);
				if(index>=0 && index<100 && size>0 && size<sizes[index]){
					read(0, ptrs[index], size);
				}
				break;
			case 3: //free no double_free
				scanf("%d", &index);
				if(index>=0 && index<100 && sizes[index]>0){
					free(ptrs[index]);
					freeable[index] = 0;
					puts(ptrs[index]); //can leak libc
				}
				break;
			default: exit(0);
		}
	}
}

tcache stashing unlink

正经的参考文档,heap_exploit_2.31,其中有poc。学习poc的好办法就是用gdb自己走一遍。这里我主要在libc源码中分析此漏洞的利用的逻辑。
在_int_malloc()中,tcache有这样一段逻辑:

设需求的size为nb个字节;
如果nb大小的tcache不满( 小于7 ),并且有2个以上nb大小的freed chunk 在smallbin中;
在_int_malloc(av, nb)过程中,会尝试把剩下的nb大小的smallbin放到tcache中

static void *
_int_malloc (mstate av, size_t bytes) {
...
	if (in_smallbin_range (nb)) {
	...
	    if ((victim = last (bin)) != bin) {
	    ...
	    	 /* While we're here, if we see other chunks of the same size,
             stash them in the tcache.  */
          size_t tc_idx = csize2tidx (nb);
          //①: 如果tchace不满
          if (tcache && tc_idx < mp_.tcache_bins) {
              mchunkptr tc_victim;
              /* While bin not empty and tcache not full, copy chunks over.  */
              //②: tcache不满且smallbin还有剩,则进入循环
              while (tcache->counts[tc_idx] < mp_.tcache_count
                     && (tc_victim = last (bin)) != bin) {
                  if (tc_victim != 0) {
                  //③: bk是攻击者控制的,故bck是目标地址附近的内存。这里没有double link check
                      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);
                    }
                }
            }
	    }
	}
...
}

tcache stashing unlink还有一些变种,但是出问题的逻辑在此。简单来说,在这里的类似unlink操作,没有像smallbin的unlink操作检查前驱或后继节点的合法性。
这样的攻击至少在2019年就已经出现在国外的ctf赛事中了,但是至今仍未打上补丁。

large bin attack

正经的参考文档,heap_exploit_2.31,其中有poc。学习poc的好办法就是用gdb自己走一遍。这里我主要在libc源码中分析此漏洞的利用的逻辑。

当unsorted bin的一个chunk进入large bin时,large bin的链表就尝试加入这个bin
于是就有
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
其中fwd->fd的chunk是攻击者控制的,其bk_nextsize可以设成(targetAddr-0x20)的位置

static void *
_int_malloc (mstate av, size_t bytes) {
...
	//① 遍历unsorted bin,依次取出放入对应的位置
     while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) {
      ...
      //② 注意main_area->last_remainder,不然直接split了
      ...
           if (in_smallbin_range (size)) {
             ...
           }
           else {
                       /* maintain large bins in sorted order */
              if (fwd != bck) {
                  /* Or with inuse bit to speed comparisons */
                  size |= PREV_INUSE;
                  /* if smaller than smallest, bypass loop below */
                  assert (chunk_main_arena (bck->bk));
                  //③ 需要走到这个分支,另一个分支已经有double link检查了,无法利用
                  if ((unsigned long) (size)
                      < (unsigned long) chunksize_nomask (bck->bk)) {
                      fwd = bck;
                      bck = bck->bk;
                      victim->fd_nextsize = fwd->fd;
                      //④ 利用的关键两行代码 fwd->fd指向的是可控的内存
                      victim->bk_nextsize = fwd->fd->bk_nextsize;
                      fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
                      ...
                      }
			...
			}
         }
      }
	      
...
}

有了上述方法,然后做什么

  • 思路1
    出题人思路:
  1. 使用large bin attack把free_hook附近写入一个堆上的地址
  2. 在1的条件下可以使用tcache stashing unlink+,把free_hook这块chunk放入tcache中
  3. 第二次使用large bin attack覆盖_IO_list_all为一个堆上地址,并在这个堆上构造好_IO_FILE
  4. 触发_IO_str_overflow(),依次执行malloc@plt, memcpy@plt, free@plt。其中三个参数都是可控的,与_IO_FILE内部数据有关。于是效果成为:
    free_hook = malloc()
    memcpy(free_hook, system_addr , n)
    free("/bin/sh")
  • 思路2
    一个参赛选手的思路:
  1. 使用large bin attack 把 _rtld_global_ptr覆盖成堆上的地址, 并在这个堆上构造好rtld_global结构体数据。(又被称为 house of banana)
  2. 利用__rtld_lock_lock_recursive或者 _dl_rtld_unlock_recursive(俗称exit_hook)为one_gadget。
  • 思路3
    笔者的思路:
  1. 使用tcache stashing unlink,把global_max_fast覆盖成一个很大的数。为了绕开大小限制
  2. fast bin attack,calloc()到rtld_global附近,覆盖__rtld_lock_lock_recursive为one_gadget。

关于fastbin attack。因为fastbin会有chunk size的检查,但是不检查对齐。但是题目大小限制,不能常规的malloc()到0x7f如此的chunk。但是可以malloc到_IO_FILE内,_IO_FILE内有0xff的chunk size。当然也可以calloc()到rtld_global附近。

这个有个大坑就是rtld_global的地址与libc的偏移,在libc-2.31是与环境相关的,而libc-2.27及以下似乎没有,原因不明。故实际解题的操作是,先打stdout泄露出线上环境的偏移,再第二次打rtld_global劫持为one_gadget。

  • 思路4
    笔者在赛场上未完成的思路,实际上应该是可以的。同样不需要large bin attack。
  1. 使用tcache stashing unlink,把global_max_fast覆盖成一个很大的数。
  2. fast bin attack,calloc()到stdout附近,完成第3步后,使用edit()功能,修改jump table为_IO_str_jump
  3. fast bin attack,calloc()到malloc_hook附近,设置为setcontext+61的位置。(libc-2.27及以前是setcontext+53)。
  4. 触发_IO_str_overflow, 进行srop。
    其中关键是,_IO_str_overflow()中malloc@plt之前,rdx寄存器是与_IO_FILE结构体有关的,受攻击者控制。
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值