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

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

2023-2-9 15:18
23236

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

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

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

本篇文章介绍的攻击主要是利用_IO_helper_overflow在执行_IO_sputn (target, s->_wide_data->_IO_write_base, used)时,3个参数均能控制,然后利用memcpy、memmove等函数实现house of 秦关汉月,其中一条链如下。

一般来说一类跳表只有一个,但_IO_helper_jumps比较特殊,通过下面可以看出,跳表会根据COMPILE_WPRINTF值不同而生成不同的,但libc在编译时会调用两次,分别是正常字符和宽字符,所以我们可以在内存中看到两个_IO_helper_jumps每种各一个。其中,COMPILE_WPRINTF==0先生成,COMPILE_WPRINTF==1后生成。

不同的COMPILE_WPRINTF所对应的helper_file也有所不同,区别在于是否需要伪造struct _IO_wide_data _wide_data;

这个函数在内存中也有2份。通过测试发现,如果使用COMPILE_WPRINTF==0的情况,在攻击过程中s->_IO_write_base会变成largebin->bk_size指针,从而在largebin attack时被强制修改从而无法控制。为了方便,我们使用COMPILE_WPRINTF==1所生成的_IO_helper_overflow。(第2个生成的)

通过上面函数可以清楚看出,在执行size_t written = _IO_sputn (target, s->_wide_data->_IO_write_base, used);

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

执行此函数内要绕过的内容较多。

需要绕过内容总结如下

执行此处需要绕过内容也比较多。

需要绕过内容总结如下

又到了喜闻乐见的抄板子时刻,此套攻击模板相对复杂很多,而且写入内容也比较长。

 
_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_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
#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
#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
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
  };
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
  };
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);
}
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
_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)
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) 

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-3-24 08:53 被我超啊编辑 ,原因: 错字
收藏
免费 6
支持
分享
最新回复 (10)
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
2
感谢分享,house_of_鬼鬼鬼鬼 
2023-2-9 15:39
1
雪    币: 4460
活跃值: (6706)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
目前还不熟悉内核,先赞一个。。
2023-2-9 16:30
0
雪    币: 1
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
大佬您好,请问一下if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)如何绕过啊
f都是同一个,他岂不是会嵌套调用嘛
2023-2-9 23:17
0
雪    币: 6028
活跃值: (3670)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
5
Aapcer 大佬您好,请问一下if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)如何绕过啊 f都是同一个,他岂不是会嵌套调用嘛
对,是嵌套调用,但不影响绕过
2023-2-10 09:54
1
雪    币: 1
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
我超啊 对,是嵌套调用,但不影响绕过
他还是会进去到_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
2023-2-10 13:37
1
雪    币: 1
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
感谢楼主XD
最后于 2023-2-10 13:43 被Aapcer编辑 ,原因:
2023-2-10 13:40
0
雪    币: 6028
活跃值: (3670)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
8
Aapcer 他还是会进去到_IO_OVERFLOW里面去,然后用f->_put_stream的来调用_IO_sputn ,而如果此时f->_putstream如果指向f的话,他的_IO_write_e ...
睿智如你
2023-2-10 20:12
0
雪    币: 1310
活跃值: (816)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
9

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

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