Featured image of post House of apple2

House of apple2

依旧参考roderick师傅

https://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/

介绍

范围:2.23至今

条件:

可以泄露heap和libc地址

可以触发IO流

可以使用largrbin attack等手法劫持_IO_list_all,vtable等地址

由于调用_wide_vtable里的函数时没有对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

house of apple2可以通过伪造vtable为_IO_wfile_jumps,wide_data为一个可控地址,_wide_vatble为one_gadget等控制程序执行流

利用_IO_wfile_overflow 函数控制程序执行流

调用链如下

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

省流

对 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

_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);// 需要走到这里
      // ......
    }
    }
}

为了走到IO_wdoallocbuf (f);我们需要绕过三个地方的if检查

需要满足 f->_flags & _IO_NO_WRITES == 0 并且 f->_flags & _IO_CURRENTLY_PUTTING == 0 和 f->_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)// _IO_WXXXX调用
      return;
  _IO_wsetb (fp, fp->_wide_data->_shortbuf,
		     fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)

我们需要走到 if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)// _IO_WXXXX调用 这里 要绕过两个if检查,需要满足 fp->_wide_data->_IO_buf_base != 0 和 fp->_flags & _IO_UNBUFFERED == 0。

_IO_WDOALLOCATE

一个宏定义,直接调用_wide_data->_wide_vtable->doallocate

1
#define _IO_WDOALLOCATE(FP) WJUMP0 (__doallocate, FP)

利用_IO_wfile_underflow_mmap 函数控制程序执行流

调用链

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

省流

对 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

_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_wfile_overflow 函数控制程序执行流

利用_IO_wdefault_xsgetn 函数控制程序执行流

调用链

1
2
3
4
5
_IO_wdefault_xsgetn
    __wunderflow
        _IO_switch_to_wget_mode
            _IO_WOVERFLOW
                *(fp->_wide_data->_wide_vtable + 0x18)(fp)

省流

对 fp 的设置如下:
● _flags 设置为 0x1800
● vtable 设置为_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

_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)。

_IO_WOVERFLOW

1
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
共生产 19 篇文章,约 39.77k 字使用 Hugo 构建
主题 Stack 由 Jimmy 设计