前言
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**)。
本文为对CatF1y大佬提出的IO_FILE调用链的学习,原文链接:House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解
原理简介
利用条件
- 能够任意写一个可控地址(如large bin attack的任意写堆地址)
- 能够泄露libc和堆地址
- 能够触发IO流(FSOP或触发__malloc_assert,或者程序中存在puts等能进入IO链的函数),执行IO相关函数。
利用原理
IO_FILE结构及利用
在高版本libc中,当攻击条件有限(如不能造成任意地址写)或者libc版本中无hook函数(libc2.34及以后)时,伪造fake_IO进行攻击是一种常见可行的攻击方式,常见的触发IO函数的方式有FSOP、__malloc_assert(当然也可以用puts等函数,只不过需要任意地址写任意值直接改掉libc中的stdout结构体),当进入IO流时会根据vtable指针调用相关的IO函数,如果在题目中造成任意地址写一个可控地址(如large bin attack、tcache stashing unlink attack、fastbin reverse into tcache),然后伪造fake_IO结构体配合恰当的IO调用链,可以达到控制程序执行流的效果。
vatable检查
1 | void _IO_vtable_check (void) attribute_hidden; |
其检查流程为:计算**_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 | static void |
[house of kiwi]([【IO_FILE】House of kiwi - 鷺雨のBlog (loora1n.github.io)](https://loora1n.github.io/2022/10/27/[IO_FILE]House of kiwi/))提供了一种调用该函数的思路,可以通过修改topchunk的大小触发,即满足下列条件中的一个
1.topchunk的大小小于MINSIZE(0X20)
2.prev inuse位为0
3.old_top页未对齐
1 | assert ((old_top == initial_top (av) && old_size == 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 | int |
触发条件则是有三种情况
FSOP有三种情况(能从main函数中返回、程序中能执行exit函数、libc中执行abort),第三种情况在高版本中已经删除;__malloc_assert则是在malloc中触发,通常是修改top chunk的大小。
一种可行的IO调用链
在_IO_wfile_jumps结构体中,会根据虚表进行相关的函数调用。
1 | const struct _IO_jump_t _IO_wfile_jumps libio_vtable = |
其中_IO_wfile_seekoff函数代码如下
1 | off64_t |
其中fp结构体是我们可以伪造的(上面那个截图的rdi,为一个堆地址),可以控制fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base来调用**_IO_switch_to_wget_mode**这个函数,继续跟进代码
1 | int |
而_IO_WOVERFLOW是glibc里定义的一个宏调用函数
1 |
对_IO_WOVERFLOW没有进行任何检测,为了便于理解,我们再来看看汇编代码
1 | 0x7f4cae745d30 <_IO_switch_to_wget_mode> endbr64 |
主要关注这几句,做了一下几点事情
- 将[rdi+0xa0]处的内容赋值给rax,为了避免与下面的rax混淆,称之为rax1。
- 将新赋值的[rax1+0x20]处的内容赋值给rdx。
- 将[rax1+0xe0]处的内容赋值给rax,称之为rax2。
- call调用[rax2+0x18]处的内容。
1 | 0x7f4cae745d34 <_IO_switch_to_wget_mode+4> mov rax, qword ptr [rdi + 0xa0] |
而rdi现在是什么状态呢?gdb调试来看看
可以看到这是一个堆地址,而实际上此时rdi就是伪造的IO结构体的地址,即之前__IO_wfile_seekoff函数传入那个fp,也是可控的。
在造成任意地址写一个堆地址的基础上,这里的寄存器rdi(fake_IO的地址)、rax和rdx都是我们可以控制的,在开启沙箱的情况下,假如把最后调用的**[rax + 0x18]设置为setcontext,把rdx设置为可控的堆地址,就能执行srop来读取flag;如果未开启沙箱,则只需把最后调用的[rax + 0x18]设置为system函数,把fake_IO的头部写入/bin/sh字符串**,就可执行system(“/bin/sh”)
fake_IO结构体需要绕过的检测
1 | _wide_data->_IO_read_ptr != _wide_data->_IO_read_end |
攻击流程
模板
house of cat的模板,原理参照上图。伪造IO结构体时只需修改fake_io_addr地址,**_IO_save_end为想要调用的函数,_IO_backup_base**为执行函数时的rdx,以及修改_flags为执行函数时的rdi;FSOP和利用__malloc_assert触发house of cat的情况不同,需要具体问题具体调整(FSOP需将vtable改为IO_wfile_jumps+0x30)
1 | fake_io_addr=heapbase+0xb00 # 伪造的fake_IO结构体的地址 |
例题:强网杯house of cat
逆向分析
整体逆向思路类似于[chunqiuIOT](i春秋春季赛-勇者巅峰-chunqiuIOT - 鷺雨のBlog (loora1n.github.io))那道题,也可以说是IO_FILE升级版,这里我们直接看核心的几个函数
- 存在UAF漏洞
- 可以申请0x470~0x418大小的chunk
- 只有两次edit的机会,且长度为0x30
- show函数固定输出0x30长度的内容
沙盒
1 | seccomp-tools dump ./houseofcat |
只能使用orw,且限制了fd为0
利用
无法退出main函数,也没有exit等能造成FSOP的方式,但是stderr不在bss上而在libc中,可以在得到libc地址后large bin attack位于libc中的stderr,再在得到heap地址的基础上修改top chunk的size,这里用large bin attack修改。所以两次edit相当于给了两次large bin attack的机会,一次用来large bin attack stderr,一次用来large bin attack topchunk’s size。另外由于对fd的检查,需要close(0)使flag文件的文件描述符为0,或者用mmap函数将flag映射读入。
- 泄露libc地址和堆地址
- large bin attack stderr
- large bin attack topchunk’s size
- 伪造fake_IO
- 触发
__malloc_assert
, 进入_IO_wfile_seekoff
转到_IO_switch_to_wget_mode
- setcontext执行rop链(orw需要注意fd应先
close(0)
)
exp
1 | from pwn import * |