首页
社区
课程
招聘
20
[原创]House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解
发表于: 2022-8-2 00:16 47154

[原创]House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解

2022-8-2 00:16
47154

House of Cat

前言

5月份偶然发现的一种新型GLIBC中IO利用思路,目前适用于任何版本(包括glibc2.35),命名为House of cat并出在2022强网杯中。

简介

House of emma是glibc2.34下常用的攻击手法之一,利用条件只需任意写一个可控地址就可以控制程序执行流,攻击威力十分强大。但是需要攻击位于TLS的_pointer_chk_guard,并且远程可能需要爆破TLS偏移。

 

House of Cat利用了House of emma的虚表偏移修改思想,通过修改虚表指针的偏移,避免了对需要绕过TLS上 _pointer_chk_guard的检测相关的IO函数的调用,转而调用_IO_wfile_jumps中的_IO_wfile_seekoff函数,然后进入到_IO_switch_to_wget_mode函数中来攻击,从而使得攻击条件和利用变得更为简单。并且house of cat在FSOP的情况下也是可行的,只需修改虚表指针的偏移来调用_IO_wfile_seekoff即可(通常是结合__malloc_assert,改vtable为_IO_wfile_jumps+0x10)。

利用条件

1.能够任意写一个可控地址。
2.能够泄露堆地址和libc基址。
3.能够触发IO流(FSOP或触发__malloc_assert,或者程序中存在puts等能进入IO链的函数),执行IO相关函数。

利用原理

IO_FILE结构及利用

在高版本libc中,当攻击条件有限(如不能造成任意地址写)或者libc版本中无hook函数(libc2.34及以后)时,伪造fake_IO进行攻击是一种常见可行的攻击方式,常见的触发IO函数的方式有FSOP、__malloc_assert(当然也可以用puts等函数,只不过需要任意地址写任意值直接改掉libc中的stdout结构体hhh),当进入IO流时会根据vtable指针调用相关的IO函数,如果在题目中造成任意地址写一个可控地址(如large bin attack、tcache stashing unlink attack、fastbin reverse into tcache),然后伪造fake_IO结构体配合恰当的IO调用链,可以达到控制程序执行流的效果。

vtable检查

在glibc2.24以后加入了对虚函数的检测,在调用虚函数之前首先会检查虚函数地址的合法性。

1
2
3
4
5
6
7
8
9
10
11
void _IO_vtable_check (void) attribute_hidden;
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  uintptr_t section_length = __stop___libc_IO_vtables -__start___libc_IO_vtables;
  uintptr_t ptr = (uintptr_t) vtable;
  uintptr_t offset = ptr -(uintptr_t)__start___libc_IO_vtables;
  if (__glibc_unlikely (offset >= section_length))
    _IO_vtable_check ();
  return vtable;
}

其检查流程为:计算_IO_vtable 段的长度(section_length),用当前虚表指针的地址减去_IO_vtable 段的开始地址,如果vtable相对于开始地址的偏移大于等于section_length,那么就会进入_IO_vtable_check进行更详细的检查,否则的话会正常调用。如果vtable是非法的,进入_IO_vtable_check函数后会触发abort。

 

虽然对vtable的检查较为严格,但是对于具体位置和具体偏移的检测则是较为宽松的,可以修改vtable指针为虚表段内的任意位置,也就是对于某一个_IO_xxx_jumps的任意偏移,使得其调用攻击者想要调用的IO函数。

__malloc_assert与FSOP

在glibc中存在一个函数_malloc_assert,其中会根据vtable表如_IO_xxx_jumps调用IO等相关函数;该函数最终会根据stderr这个IO结构体进行相关的IO操作
图片描述
代码如下

1
2
3
4
5
6
7
8
9
10
11
12
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
         const char *function)
{
  (void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
             __progname, __progname[0] ? ": " : "",
             file, line,
             function ? function : "", function ? ": " : "",
             assertion);
  fflush (stderr);
  abort ();
}

house of kiwi提供了一种调用该函数的思路,可以通过修改topchunk的大小触发,即满足下列条件中的一个

1.topchunk的大小小于MINSIZE(0X20)
2.prev inuse位为0
3.old_top页未对齐

1
2
3
4
assert ((old_top == initial_top (av) && old_size == 0) ||
        ((unsigned long) (old_size) >= MINSIZE &&
         prev_inuse (old_top) &&
         ((unsigned long) old_end & (pagesize - 1)) == 0));

下面介绍另一种触发house of cat的方式FSOP

 

程序中所有的_IO_FILE 结构用_chain连接形成一个单链表,链表的头部则是_IO_list_all

 

FSOP就是通过劫持_IO_list_all的值(如large bin attack修改)来执行_IO_flush_all_lockp函数,这个函数会根据_IO_list_all刷新链表中的所有文件流,在libc中代码如下,其中会调用vtable中的IO函数_IO_OVERFLOW,根据我们上面所说的虚表偏移可变思想,这个地方的虚表偏移也是可修改的,然后配合伪造IO结构体可以执行house of cat的调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
_IO_flush_all_lockp (int do_lock)
{
  ...
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
  {
       ...
       if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
               && _IO_OVERFLOW (fp, EOF) == EOF)
           {
               result = EOF;
          }
        ...
  }
}

触发条件则是有三种情况

FSOP有三种情况(能从main函数中返回、程序中能执行exit函数、libc中执行abort),第三种情况在高版本中已经删除;__malloc_assert则是在malloc中触发,通常是修改top chunk的大小。

一种可行的IO调用链

在_IO_wfile_jumps结构体中,会根据虚表进行相关的函数调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_new_file_finish),
  JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
  JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
  JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
  JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
  JUMP_INIT(xsputn, _IO_wfile_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_wfile_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
  JUMP_INIT(doallocate, _IO_wfile_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};

其中_IO_wfile_seekoff函数代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
  off64_t result;
  off64_t delta, new_offset;
  long int count;
 
  if (mode == 0)
    return do_ftell_wide (fp);
  int must_be_exact = ((fp->_wide_data->_IO_read_base
            == fp->_wide_data->_IO_read_end)
               && (fp->_wide_data->_IO_write_base
               == fp->_wide_data->_IO_write_ptr));
#需要绕过was_writing的检测
  bool was_writing = ((fp->_wide_data->_IO_write_ptr
               > fp->_wide_data->_IO_write_base)
              || _IO_in_put_mode (fp));
 
  if (was_writing && _IO_switch_to_wget_mode (fp))
    return WEOF;
......
}

其中fp结构体是我们可以伪造的,可以控制fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base来调用_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;
  ......
}

[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2023-3-9 15:00 被CatF1y编辑 ,原因:
上传的附件:
收藏
免费 20
支持
分享
赞赏记录
参与人
雪币
留言
时间
衣昂扬
感谢你分享这么好的资源!
2025-2-13 18:18
mick0960
为你点赞~
2024-7-7 22:59
小旺不正经
为你点赞~
2023-12-13 10:11
zhczf
为你点赞~
2023-12-13 08:30
XDLiu
为你点赞~
2023-11-12 16:15
StardyGod
为你点赞~
2023-7-27 09:55
Ro1ME
为你点赞~
2023-5-24 20:44
mb_ancrzczt
为你点赞~
2023-5-23 17:18
anrorua
为你点赞~
2023-5-23 11:33
偏安一隅
为你点赞~
2023-5-23 11:00
Sanntaa
为你点赞~
2023-5-23 10:42
bwner
为你点赞~
2023-5-17 16:50
Jmp.Cliff
为你点赞~
2023-5-6 18:15
伟叔叔
为你点赞~
2023-3-18 01:40
PLEBFE
为你点赞~
2023-1-12 01:37
Nameless_a
为你点赞~
2022-9-29 23:52
than0s
为你点赞~
2022-8-28 14:27
jmpcall
为你点赞~
2022-8-25 09:27
evilpan
为你点赞~
2022-8-3 17:00
CatF1y
为你点赞~
2022-8-2 00:38
打赏 + 150.00雪花
打赏次数 1 雪花 + 150.00
收起 
赞赏  Editor   +150.00 2022/08/29 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (13)
雪    币: 1143
活跃值: (3126)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2022-8-2 13:34
0
雪    币: 134
活跃值: (259)
能力值: ( LV2,RANK:11 )
在线值:
发帖
回帖
粉丝
3

请问这里的mode参数可控吗?或者说如何绕过mode==0的检测。

2022-8-25 08:53
0
雪    币: 2267
活跃值: (1553)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
4
wx_唐_464 请问这里的mode参数可控吗?或者说如何绕过mode==0的检测。

参数是可控的,mode为0或为1最终都会执行到后面的函数,不影响结果,我试过了

最后于 2022-8-25 14:41 被CatF1y编辑 ,原因:
2022-8-25 14:40
1
雪    币: 35
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5

应该是可以控制mode,实际上也就是控制rcx寄存器,我调试跟进的时候发现执行中间有对rcx的写操作,如果rcx寄存器为0的话构造两个FILE结构体然后第一个用来置rcx,第二个用来调用就能执行到_IO_switch_to_wget_mode

2022-9-6 20:46
1
雪    币: 2267
活跃值: (1553)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
6
Phot0n 应该是可以控制mode,实际上也就是控制rcx寄存器,我调试跟进的时候发现执行中间有对rcx的写操作,如果rcx寄存器为0的话构造两个FILE结构体然后第一个用来置rcx,第二个用来调用就能执行到_I ...
为何要构造两个结构体?
2022-9-6 22:24
1
雪    币: 35
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7

我自己本地构建demo测试的时候很不巧mode也就是rcx好像是=0的(这个mode不是FILE结构体里的mode,应该是rcx寄存器),然后跳到了另外一个分支也就是 

1
2
if (mode == 0)    
    return do_ftell_wide (fp);,

所以我堆上相邻的构造了两个FILE结构(内容大致相似,只是相应地址改变),第一个chain指向第二个,希望在两次_IO_wfile_seekoff之间能够让rcx寄存器改变,使得虽然对第一个FILE进行_IO_flush_all_lockp操作的时候没有进入_IO_switch_to_wget_mode,然后对第二个FILE进行操作的时候进入。但是发现相邻两次_IO_wfile_seekoff的rcx寄存器值似乎并没有改变,然后单步调试的时候发现存在对从堆相关地址处取值赋给rcx的操作,于是在不改变分支的情况下修改相应堆上的值就行了


(这只是我个人比价简陋的想法,应该有其他控制rcx寄存器的方法

2022-9-7 10:56
0
雪    币: 2267
活跃值: (1553)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
8
Phot0n 我自己本地构建demo测试的时候很不巧mode也就是rcx好像是=0的(这个mode不是FILE结构体里的mode,应该是rcx寄存器),然后跳到了另外一个分支也就是&nbsp;if& ...
直接在第一个结构体控制mode=1就可以了,之前我的版本发现mode=0不影响后面的执行就没管
2022-10-7 14:38
1
雪    币: 277
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9


最后于 2023-6-1 19:04 被anrorua编辑 ,原因:
2023-5-25 09:21
1
雪    币: 277
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10


最后于 2023-5-25 17:20 被anrorua编辑 ,原因:
2023-5-25 09:23
1
雪    币: 28
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
当top_chunk的prev_inused位为0时可以触发malloc_assert吗,怎么我调试的不对,,,,

2023-7-26 20:38
0
雪    币: 3972
活跃值: (31426)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
感谢分享
2023-7-27 08:57
1
雪    币: 25
活跃值: (324)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13

后排提醒,模板里第5、6行容易造成困扰。 

1
2
fake_IO_FILE +=p64(1)+p64(2# rcx!=0(FSOP) 
fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx

这两句写的简直是驴唇不对马嘴。

首先wide_date指向了fake_FILE+0x30, 那么在第五行第二个数p64(2)的位置是 _wide_data->_IO_write_base

在第六行 rdx的位置是_wide_data->_IO_write_ptr

已知要保证_IO_write_ptr>_IO_write_base。

如果要用ORW,那么rdx是一个地址,是一个大数,只需要把第五行两个数设为0即可。

1
2
fake_IO_FILE +=p64(0)+p64(0
fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx

如果使用system/onegadget,虽然不需要控rdx,也要把rdx填入数字,保证大于第五行。

1
2
fake_IO_FILE +=p64(0)+p64(0
fake_IO_FILE +=p64(1)           #rdx

不知道原本填的1和2有什么意义,刚开始还以为第五行才是_IO_write_base和_IO_write_ptr,整了半天原来第五行没啥用。

而且第五行两个值也不是控rcx的,rcx的值只和mode、_IO_helper_jumps、fake_vtable地址有关,是两个libc地址的差值(修改的位置在IO_validate_vtable函数里),和第五行两个数没什么关系。




最后于 2023-12-13 01:18 被呼风唤雨编辑 ,原因:
2023-12-13 01:09
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
只要让flag&0x2!=0就能进入buffered_vfprintf,该函数稳定让mode!=0
2024-6-1 16:31
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册