house of apple续
196082 慢慢好起来

死都想不到house of apple居然有三篇文章,这里就把第二篇和第三篇合并起来一起学完吧。

house of apple2

利用条件:

  1. 已知heap地址和glibc地址

  2. 能控制程序执行IO操作,包括但不限于:从main函数返回、调用exit函数、通过__malloc_assert触发

  3. 能控制_IO_FILEvtable_wide_data,一般使用largebin attack去控制

利用原理

https://cv196082.gitee.io/2022/02/23/FSOP/ 这篇文章中详细介绍了去调用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;
};

注意_IO_wide_data结构体中存在_wide_vtable成员,在调用_wide_vtable虚表是同样会经过一系列宏去调用:

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

在这里调用的顺序中没有出现vtable是否合法的检测,因此我们可以劫持_IO_FILE的vtable为_IO_wfile_jumps,控制_wide_data为可以控制的堆空间,进一步控制_wide_data->_wide_vtable指向可以控制的堆地址,控制程序IO流函数调用,最终调用到IO_wxxxxx

利用思路

这里最终目的是调用_wide_vtable成员,所以需要找到上述宏的调用,最终发现只存在以下四个:_IO_WSETBUF、_IO_WUNDERFLOW、_IO_WDOALLOCATE、_IO_WOVERFLOW

并且其中前两个还是利用难度较高,甚至是无法利用。下面从原文的三个方向出发

_IO_wfile_overflow

这里的调用链为:_IO_wfile_overflow=>_IO_wdoallocbuf=>_IO_WDOALLOCATE=>*(fp->_wide_data->_wide_vtable+ 0x68)(fp)

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 == 0f->_wide_data->_IO_write_base == 0

接着看_IO_wdoallocbuf

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)
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)

这里就调用了IO_Wxxxx并且需要满足fp->_wide_data->_IO_buf_base != 0fp->_flags & _IO_UNBUFFERED == 0

所以总的来说构造方式为:

  1. _flags = ~(2 | 0x8 | 0x800)即可,所以可以直接设置为0或者这值为 sh;
  2. vtable设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap地址(加减偏移),使其能成功调用_IO_wfile_overflow即可
  3. _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  4. _wide_data->_IO_write_base设置为0,即满足*(A + 0x18) = 0
  5. _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
  6. _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  7. _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C

_IO_wfile_underflow_mmap

调用链:_IO_wfile_underflow_mmap=>_IO_wdoallocbuf=>_IO_WDOALLOCATE=>*(fp->_wide_data->_wide_vtable +0x68)(fp)

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 == NULLfp->_wide_data->_IO_save_base == NULL

构造方式:

  1. _flags设置为~4,如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;
  2. vtable设置为_IO_wfile_jumps_mmap地址(加减偏移),使其能成功调用_IO_wfile_underflow_mmap即可
  3. _IO_read_ptr < _IO_read_end,即满足*(fp + 8) < *(fp + 0x10)
  4. _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  5. _wide_data->_IO_read_ptr >= _wide_data->_IO_read_end,即满足*A >= *(A + 8)
  6. _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
  7. _wide_data->_IO_save_base设置为0或者合法的可被free的地址,即满足*(A + 0x40) = 0
  8. _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  9. _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C

_IO_wdefault_xsgetn

调用链:_IO_wdefault_xsgetn=>__wunderflow=>_IO_switch_to_wget_mode=>_IO_WOVERFLOW=>*(fp->_wide_data->_wide_vtable+0x18)(fp)

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)

需要设置fp->_wide_data->_IO_read_ptr == fp->_wide_data->_IO_read_end,使得count0,不进入if分支。

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;
......
}

需要设置fp->mode > 0,并且fp->_flags & _IO_CURRENTLY_PUTTING != 0

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

构造方式:

  1. _flags设置为0x800
  2. vtable设置为_IO_wstrn_jumps/_IO_wmem_jumps/_IO_wstr_jumps地址(加减偏移),使其能成功调用_IO_wdefault_xsgetn即可
  3. _mode设置为大于0,即满足*(fp + 0xc0) > 0
  4. _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  5. _wide_data->_IO_read_end == _wide_data->_IO_read_ptr设置为0,即满足*(A + 8) = *A
  6. _wide_data->_IO_write_ptr > _wide_data->_IO_write_base,即满足*(A + 0x20) > *(A + 0x18)
  7. _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  8. _wide_data->_wide_vtable->overflow设置为地址C用于劫持RIP,即满足*(B + 0x18) = C

house of apple3

前两篇文章中的利用链主要关注_wide_data成员,而本篇文章并不会特别关注_wide_data,而是关注FILE结构体的另外一个成员_codecvt的利用。

利用条件

  1. 已知heap地址和glibc地址
  2. 能控制程序执行IO操作,包括但不限于:从main函数返回、调用exit函数、通过__malloc_assert触发
  3. 能控制_IO_FILEvtable_codecvt,一般使用largebin attack去控制

注意

上面提到,本篇文章并不会特别关注_wide_data成员,这是因为_wide_data设置不当的话会影响某些利用链的分支走向。但是,如果采用默认的_wide_data成员(默认会指向_IO_wide_data_2,除了_wide_vtable外其他成员均默认为0),也并不影响house of apple3的利用。因此,如果能伪造整个FILE结构体,则需要设置合适的_wide_data;如果只能伪部分FILE的成员的话,保持fp->_wide_data为默认地址即可。

利用原理

FILE结构体中有一个成员struct _IO_codecvt *_codecvt;,偏移为0x98

1
2
3
4
5
struct _IO_codecvt
{
_IO_iconv_t __cd_in;
_IO_iconv_t __cd_out;
};

__cd_in__cd_out是同一种类型的数据。

1
2
3
4
5
typedef struct
{
struct __gconv_step *step;
struct __gconv_step_data step_data;
} _IO_iconv_t;

再观察以下结构体俩变量的定义:

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
struct __gconv_step
{
struct __gconv_loaded_object *__shlib_handle;
const char *__modname;

/* For internal use by glibc. (Accesses to this member must occur
when the internal __gconv_lock mutex is acquired). */
int __counter;

char *__from_name;
char *__to_name;

__gconv_fct __fct;
__gconv_btowc_fct __btowc_fct;
__gconv_init_fct __init_fct;
__gconv_end_fct __end_fct;

/* Information about the number of bytes needed or produced in this
step. This helps optimizing the buffer sizes. */
int __min_needed_from;
int __max_needed_from;
int __min_needed_to;
int __max_needed_to;

/* Flag whether this is a stateful encoding or not. */
int __stateful;

void *__data; /* Pointer to step-local data. */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct __gconv_step_data
{
unsigned char *__outbuf; /* Output buffer for this step. */
unsigned char *__outbufend; /* Address of first byte after the output
buffer. */

/* Is this the last module in the chain. */
int __flags;

/* Counter for number of invocations of the module function for this
descriptor. */
int __invocation_counter;

/* Flag whether this is an internal use of the module (in the mb*towc*
and wc*tomb* functions) or regular with iconv(3). */
int __internal_use;

__mbstate_t *__statep;
__mbstate_t __state; /* This element must not be used directly by
any module; always use STATEP! */
};

house of apple3的利用主要关注以下三个函数:__libio_codecvt_out__libio_codecvt_in__libio_codecvt_length。三个函数的利用点都差不多

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
enum __codecvt_result
__libio_codecvt_in (struct _IO_codecvt *codecvt, __mbstate_t *statep,
const char *from_start, const char *from_end,
const char **from_stop,
wchar_t *to_start, wchar_t *to_end, wchar_t **to_stop)
{
enum __codecvt_result result;
struct __gconv_step *gs = codecvt->__cd_in.step;
int status;
size_t dummy;
const unsigned char *from_start_copy = (unsigned char *) from_start;

codecvt->__cd_in.step_data.__outbuf = (unsigned char *) to_start;
codecvt->__cd_in.step_data.__outbufend = (unsigned char *) to_end;
codecvt->__cd_in.step_data.__statep = statep;

__gconv_fct fct = gs->__fct;
#ifdef PTR_DEMANGLE
if (gs->__shlib_handle != NULL)
PTR_DEMANGLE (fct);
#endif
status = DL_CALL_FCT (fct,
(gs, &codecvt->__cd_in.step_data, &from_start_copy,
(const unsigned char *) from_end, NULL,
&dummy, 0, 0));
......
}
1
2
3
4
5
6
7
typedef int (*__gconv_fct) (struct __gconv_step *, struct __gconv_step_data *,
const unsigned char **, const unsigned char *,
unsigned char **, size_t *, int, int);

#ifndef DL_CALL_FCT
# define DL_CALL_FCT(fct, args) fct args
#endif

这里呢最后这个宏就是调用fct (gs, …)

_IO_wfile_underflow函数中调用了__libio_codecvt_in

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
wint_t
_IO_wfile_underflow (FILE *fp)
{
struct _IO_codecvt *cd;
enum __codecvt_result status;
ssize_t count;

/* C99 requires EOF to be "sticky". */

if (fp->_flags & _IO_EOF_SEEN)
return WEOF;
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)
{
/* There is more in the external. Convert it. */
const char *read_stop = (const char *) fp->_IO_read_ptr;

fp->_wide_data->_IO_last_state = fp->_wide_data->_IO_state;
fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_read_ptr =
fp->_wide_data->_IO_buf_base;
status = __libio_codecvt_in (cd, &fp->_wide_data->_IO_state,
fp->_IO_read_ptr, fp->_IO_read_end,
&read_stop,
fp->_wide_data->_IO_read_ptr,
fp->_wide_data->_IO_buf_end,
&fp->_wide_data->_IO_read_end);
......
}
}

_IO_wfile_underflow又是_IO_wfile_jumps这个_IO_jump_t类型变量的成员函数

所以总的来说利用方式为:劫持或者伪造FILE结构体的fp->vtable_IO_wfile_jumpsfp->_codecvt为可控堆地址,当程序执行IO操作时,控制程序执行流走到_IO_wfile_underflow,设置好fp->codecvt->__cd_in结构体,使得最终调用到__libio_codecvt_in中的DL_CALL_FCT宏,伪造函数指针,进而控制程序执行流。需要注意的是设置gs->__shlib_handle == NULL绕过__pointer_guard指针的加密保护

利用思路

因为原文作者确实非常牛逼,甚至链都找好了,所以这里还是贴上原文的内容

_IO_wfile_underflow

调用链:_IO_wfile_underflow=>__libio_codecvt_in=>DL_CALL_FCT=>gs =fp->_codecvt->__cd_in.step=>*(gs->__fct)(gs)

这条链是在利用原理当作示例的一条,所以这里只给出构造方式:

  1. _flags设置为~(4 | 0x10)
  2. vtable设置为_IO_wfile_jumps地址(加减偏移),使其能成功调用_IO_wfile_underflow即可
  3. fp->_IO_read_ptr < fp->_IO_read_end,即满足*(fp + 8) < *(fp + 0x10)
  4. _wide_data保持默认,或者设置为堆地址,假设其地址为A,即满足*(fp + 0xa0) = A
  5. _wide_data->_IO_read_ptr >= _wide_data->_IO_read_end,即满足*A >= *(A + 8)
  6. _codecvt设置为可控堆地址B,即满足*(fp + 0x98) = B
  7. codecvt->__cd_in.step设置为可控堆地址C,即满足*B = C
  8. codecvt->__cd_in.step->__shlib_handle设置为0,即满足*C = 0
  9. codecvt->__cd_in.step->__fct设置为地址D,地址D用于控制rip,即满足*(C + 0x28) = D。当调用到D的时候,此时的rdiC。如果_wide_data也可控的话,rsi也能控制。

_IO_wfile_underflow_mmap

调用链:_IO_wfile_underflow_mmap=>__libio_codecvt_in=>DL_CALL_FCT=>gs=fp->_codecvt->__cd_in.step=>*(gs->__fct)(gs)

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
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->_wide_data->_IO_last_state = fp->_wide_data->_IO_state;
fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_read_ptr =
fp->_wide_data->_IO_buf_base;

__libio_codecvt_in (cd, &fp->_wide_data->_IO_state,
fp->_IO_read_ptr, fp->_IO_read_end,
&read_stop,
fp->_wide_data->_IO_read_ptr,
fp->_wide_data->_IO_buf_end,
&fp->_wide_data->_IO_read_end);
......
}

需要设置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不进入调用。

构造方法:

  1. _flags设置为~4
  2. vtable设置为_IO_wfile_jumps_mmap地址(加减偏移),使其能成功调用_IO_wfile_underflow_mmap即可
  3. _IO_read_ptr < _IO_read_end,即满足*(fp + 8) < *(fp + 0x10)
  4. _wide_data保持默认,或者设置为堆地址,假设其地址为A,即满足*(fp + 0xa0) = A
  5. _wide_data->_IO_read_ptr >= _wide_data->_IO_read_end,即满足*A >= *(A + 8)
  6. _wide_data->_IO_buf_base设置为非0,即满足*(A + 0x30) != 0
  7. _codecvt设置为可控堆地址B,即满足*(fp + 0x98) = B
  8. codecvt->__cd_in.step设置为可控堆地址C,即满足*B = C
  9. codecvt->__cd_in.step->__shlib_handle设置为0,即满足*C = 0
  10. codecvt->__cd_in.step->__fct设置为地址D,地址D用于控制rip,即满足*(C + 0x28) = D。当调用到D的时候,此时的rdiC。如果_wide_data也可控的话,rsi也能控制。

_IO_wdo_write

调用链:_IO_new_file_sync=>_IO_do_flush=>_IO_wdo_write=>__libio_codecvt_out=>DL_CALL_FCT=>gs = fp->_codecvt->__cd_out.step=>*(gs->__fct)(gs)

1
2
3
4
5
6
7
8
9
10
11
int
_IO_new_file_sync (FILE *fp)
{
ssize_t delta;
int retval = 0;

/* char* ptr = cur_ptr(); */
if (fp->_IO_write_ptr > fp->_IO_write_base)
if (_IO_do_flush(fp)) return EOF;
......
}

满足fp->_IO_write_ptr > fp->_IO_write_base

1
2
3
4
5
6
7
#define _IO_do_flush(_f) \
((_f)->_mode <= 0 \
? _IO_do_write(_f, (_f)->_IO_write_base, \
(_f)->_IO_write_ptr-(_f)->_IO_write_base) \
: _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base, \
((_f)->_wide_data->_IO_write_ptr \
- (_f)->_wide_data->_IO_write_base)))

使fp->_mode > 0

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
int
_IO_wdo_write (FILE *fp, const wchar_t *data, size_t to_do)
{
struct _IO_codecvt *cc = fp->_codecvt;

if (to_do > 0)
{
if (fp->_IO_write_end == fp->_IO_write_ptr
&& fp->_IO_write_end != fp->_IO_write_base)
{
if (_IO_new_do_write (fp, fp->_IO_write_base,
fp->_IO_write_ptr - fp->_IO_write_base) == EOF)
return WEOF;
}

......

/* Now convert from the internal format into the external buffer. */
result = __libio_codecvt_out (cc, &fp->_wide_data->_IO_state,
data, data + to_do, &new_data,
write_ptr,
buf_end,
&write_ptr);
......
}
}

满足fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base,然后这个判断需要为假fp->_IO_write_end == fp->_IO_write_ptr && fp->_IO_write_end != fp->_IO_write_base

构造方式:

  1. vtable设置为_IO_file_jumps地址(加减偏移),使其能成功调用_IO_new_file_sync即可
  2. _IO_write_ptr > _IO_write_base,即满足*(fp + 0x28) > *(fp + 0x20)
  3. _mode > 0,即满足(fp + 0xc0) > 0
  4. _IO_write_end != _IO_write_ptr或者_IO_write_end == _IO_write_base,即满足*(fp + 0x30) != *(fp + 0x28)或者*(fp + 0x30) == *(fp + 0x20)
  5. _wide_data设置为堆地址,假设地址为A,即满足*(fp + 0xa0) = A
  6. _wide_data->_IO_write_ptr >= _wide_data->_IO_write_base,即满足*(A + 0x20) >= *(A + 0x18)
  7. _codecvt设置为可控堆地址B,即满足*(fp + 0x98) = B
  8. codecvt->__cd_out.step设置为可控堆地址C,即满足*(B + 0x38) = C
  9. codecvt->__cd_out.step->__shlib_handle设置为0,即满足*C = 0
  10. codecvt->__cd_out.step->__fct设置为地址D,地址D用于控制rip,即满足*(C + 0x28) = D。当调用到D的时候,此时的rdiC。如果_wide_data也可控的话,rsi也能控制。

_IO_wfile_sync

调用链:_IO_wfile_sync=>__libio_codecvt_length=>DL_CALL_FCT=>gs = fp->_codecvt->__cd_in.step=>*(gs->__fct)(gs)

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
wint_t
_IO_wfile_sync (FILE *fp)
{
ssize_t delta;
wint_t retval = 0;

/* char* ptr = cur_ptr(); */
if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
if (_IO_do_flush (fp))
return WEOF;
delta = fp->_wide_data->_IO_read_ptr - fp->_wide_data->_IO_read_end;
if (delta != 0)
{
/* We have to find out how many bytes we have to go back in the
external buffer. */
struct _IO_codecvt *cv = fp->_codecvt;
off64_t new_pos;

int clen = __libio_codecvt_encoding (cv);

if (clen > 0)
/* It is easy, a fixed number of input bytes are used for each
wide character. */
delta *= clen;
else
{
/* We have to find out the hard way how much to back off.
To do this we determine how much input we needed to
generate the wide characters up to the current reading
position. */
int nread;
size_t wnread = (fp->_wide_data->_IO_read_ptr
- fp->_wide_data->_IO_read_base);
fp->_wide_data->_IO_state = fp->_wide_data->_IO_last_state;
nread = __libio_codecvt_length (cv, &fp->_wide_data->_IO_state,
fp->_IO_read_base,
fp->_IO_read_end, wnread);
......

}
}
}

设置fp->_wide_data->_IO_write_ptr <= fp->_wide_data->_IO_write_basefp->_wide_data->_IO_read_ptr - fp->_wide_data->_IO_read_end != 0。可以看到这里还需要绕过__libio_codecvt_encoding函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int
__libio_codecvt_encoding (struct _IO_codecvt *codecvt)
{
/* See whether the encoding is stateful. */
if (codecvt->__cd_in.step->__stateful)
return -1;
/* Fortunately not. Now determine the input bytes for the conversion
necessary for each wide character. */
if (codecvt->__cd_in.step->__min_needed_from
!= codecvt->__cd_in.step->__max_needed_from)
/* Not a constant value. */
return 0;

return codecvt->__cd_in.step->__min_needed_from;
}

设置fp->codecvt->__cd_in.step->__stateful != 0即可返回-1

构造方式:

  1. _flags设置为~(4 | 0x10)
  2. vtable设置为_IO_wfile_jumps地址(加减偏移),使其能成功调用_IO_wfile_sync即可
  3. _wide_data设置为堆地址,假设其地址为A,即满足*(fp + 0xa0) = A
  4. _wide_data->_IO_write_ptr <= _wide_data->_IO_write_base,即满足*(A + 0x20) <= *(A + 0x18)
  5. _wide_data->_IO_read_ptr != _wide_data->_IO_read_end,即满足*A != *(A + 8)
  6. _codecvt设置为可控堆地址B,即满足*(fp + 0x98) = B
  7. codecvt->__cd_in.step设置为可控堆地址C,即满足*B = C
  8. codecvt->__cd_in.step->__stateful设置为非0,即满足*(B + 0x58) != 0
  9. codecvt->__cd_in.step->__shlib_handle设置为0,即满足*C = 0
  10. codecvt->__cd_in.step->__fct设置为地址D,地址D用于控制rip,即满足*(C + 0x28) = D。当调用到D的时候,此时的rdiC。如果rsi&codecvt->__cd_in.step_data可控。

总结

house of apple1主要作用就是实现任意地址写,而我认为house of apple2是最吊的,一定程度上减少了覆盖这个SB pointer_guard。而house of apple3在构造方面要求相对来说较为苛刻。(我还跟个SB一样准备自己写pack_file函数来构造IO_FILE结构体,但是我发现pwntools居然有FileStructure()这个函数可以直接生成)


参考链接:

https://bbs.pediy.com/thread-273832.htm#msg_header_h3_2

https://bbs.pediy.com/thread-273863.htm#msg_header_h3_3

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量