一直很想写一篇IO_FILE的利用总结,虽然写的有点晚了,希望写这一篇总结可以对IO_FILE有一个深入的了解吧…
文件结构
在libc中,文件结构由一个叫_IO_FILE_plus的结构体维护,它包含一个_IO_FILE结构体和一个指向函数跳转表的指针。从注释我们也可以知道,vtable指向的函数跳转表是为了与C++的streambuf兼容,当程序对某个流进行操作时,会对应调用函数调用表中对应的函数。
1 | //glibc-2.23 ./libio/libioP.h |
_IO_FILE结构体的具体成员如下:
1 | //glibc-2.23 ./libio/libio.h |
这个函数跳转表由结构体_IO_jump_t维护,具体成员如下:
1 | //glibc-2.23 ./libio/libioP.h |
程序所有的FILE结构会通过_IO_FILE结构体中的成员_chain链成一个链表,其头部为全局变量_IO_list_all。
HITCON 2016 house of orange
待补充
how2heap house of orange
题目概述
编译及运行情况:
1 | $ gcc ./house_of_orange.c -o house_of_orange |
利用过程分析
House of orange假设堆上存在一个缓冲区溢出,top chunk可以被破坏。初始时通常top chunk的大小为0x21000,随着堆的不断分配,top chunk逐渐变小,当top chunk的大小小于请求分配的大小时,会有两种可能性,一是以brk的形式扩展top chunk,二是使用mmap创建独立的的page。但如果申请的chunk大小小于mmap_threshold时,会进行top chunk的扩展,mmap_threshold是128KB。
首先申请一个大小为0x400的chunk p1,,从top chunk中分配了0x400,此时top chunk的大小和prev_inuse位组合为0x20c01。
1 | p1 = malloc(0x400-16); //0x400 |
1 | pwndbg> heap |
将top chunk的大小由0x20c01改为0xc01。
1 | top = (size_t *) ( (char *) p1 + 0x400 - 16); |
1 | pwndbg> x /8gx 0x602000 #chunk p1 |
对于伪造top chunk的大小有一些要求:
1 | assert((old_top == initial_top(av) && old_size == 0) || |
堆的边界必须是页面对齐的,因为top chunk是堆上的最后一个块,所以它必须在末尾进行页面对齐。另外,如果要释放与top chunk相邻的chunk,则该chunk会与top chunk合并,因此top chunk的PREV_INUSE必须为1,所以总结来说,top chunk的大小有以下要求。
1 | size要大于MINSIZE(0x20) |
当我们请求一个大于top chunk但又小于mmap_threshold的chunk时,会强制调用sysmalloc,最终调用_int_free。
1 | p2 = malloc(0x1000); |
1 | //glibc-2.25 ./malloc/malloc.c |
malloc会分配另外一个page作为新的top chunk,新的top chunk与原来的top chunk相邻,原来的top chunk会进入unsorted bin中。原来堆的大小:
top chunk扩展之后:
原来的top chunk进入unsorted bin中,原来的top chunk大小为0x21000,从0x623000进行top chunk的扩展,与原来的top chunk相邻,在新的top chunk中分配0x1000,即chunk p1。
1 | pwndbg> x /8gx 0x623000 #chunk p1 |
下面开始第二阶段的利用,这里利用的是每当触发_IO_flush_all_lockp时,_IO_flush_all_lockp刷新所有的文件指针,即遍历_IO_list_all链中的每一项,分别调用_IO_OVERFLOW。所以这个POC的想法是使用伪文件指针覆盖_IO_list_all链中的文件指针,该fake FILE的_IO_OVERFLOW指向system,该FILE结构的前8个字节设置为“/bin/sh”,所以当调用_IO_OVERFLOW(fp, EOF)其实是调用system(“/bin/sh”)。这是第二阶段的思路。libc触发_IO_flush_all_lockp有以下三种情况:
1 | libc触发abort时 |
首先需要修改_IO_list_all中的内容,伪造一个fake _IO_FILE,这里利用UnsortedBin Attack将_IO_list_all的值修改为main_arena+0x58,然后在main_arena中构造fake _IO_FILE。假设有一个上文中的溢出可以修改top chunk,在第二阶段中再次使用此溢出来覆盖释放到unsorted bin中的旧的top chunk的fd和bk指针,将bk指针修改为_IO_list_all-0x10,这样在后续试图分割unsortedbin中的free chunk来满足分配请求时,该free chunk->bk->fd会被覆盖到main_arena的地址空间中,也就是我们所说的UnsortedBin Attack。
1 | //glibc-2.23 ./malloc/malloc.c |
1 | io_list_all = top[2] + 0x9a8; |
覆盖之前该chunk的fd和bk指针的状态是指向main_arena,_IO_list_all中的内容如下:
1 | pwndbg> x /8gx 0x602400 #old top chunk |
覆盖之后,_IO_list_all的值修改为main_arena+0x58。
1 | pwndbg> p &_IO_list_all |
在执行_IO_flush_all_lockp时,会通过_IO_FILE结构体的_chain成员寻找下一个_IO_FILE,如果_IO_list_all的值修改为main_arena+0x58时,_chain成员偏移位置恰好在smallbin[4]的头部。如果将旧的top chunk的大小修改为0x61(prev_inuse位必须为1),当请求分配一个较小的分配时,unsorted bin中的chunk会进入对应大小的bin中,在64位系统中,0x60恰好是smallbin[4],前面依次是0x20、0x30、0x40、0x50。而smallbin[4]中是空的,因此旧的top chunk将在smallbin[4]的头部。也就进入以_IO_list_all为头部的_IO_FILE链中。
所以要修改旧的top chunk的大小为0x61,并在旧的top chunk中伪造_IO_FILE结构,参考_IO_flush_all_lockp源码,想要调用_IO_OVERFLOW (fp, EOF),_IO_FILE需要满足几个条件:
1 | //glibc-2.23 ./libio/genops.c |
根据libc中的_IO_flush_all_lockp实现可以知道需要满足以下条件:
1 | fp->_mode <= 0 |
在POC中是选择满足了第一种条件:
1 | top[1] = 0x61; //修改top chunk的size域为0x61。 |
接下来考虑如何将fake _IO_FILE的_IO_OVERFLOW指向system,首先fake FILE的前8个字节是”/bin/sh”:
1 | memcpy( ( char *) top, "/bin/sh\x00", 8); |
考虑_IO_flush_all_lockp是如何找到_IO_FILE的_IO_OVERFLOW的呢?在前面文件结构的介绍可知是通过虚表指针vtable,vtable的地址位于_IO_FILE之后,也就是base_address+sizeof(_IO_FILE) = jump_table,在libc2.23中,32位vtable在_IO_FILE_plus结构体的偏移是0x94,64位偏移是0xd8。其中,_IO_OVERFLOW函数指针在结构体_IO_jump_t中偏移为0x18,下标为3。在POC中是这样构造的,将_IO_OVERFLOW指针指向winner函数,winner函数中调用了system函数。
1 | size_t *jump_table = &top[12]; // controlled memory |
最后申请堆内存触发chunks进入对应的bins,旧的top chunk进入smallbin[4]中,我们伪造的fake _IO_FILE进入_IO_list_all中。
1 | malloc(10); |
在main_arena+0x58处伪造的_IO_list_all如下所示,_chain中指向下一个_IO_FILE,也就是我们在旧的top chunk中伪造的fake _IO_FILE。
1 | pwndbg> p (*(struct _IO_FILE_plus*) 0x7ffff7dd1b78) #main_arena+0x58 |
在旧的top chunk中伪造的_IO_FILE结构如下所示,续表指针指向0x602460。
1 | pwndbg> p (*(struct _IO_FILE_plus*) 0x602400) |
在0x602460处的函数跳转表中,第三项为_IO_OVERFLOW函数指针,指向winner函数,调用winner(“/bin/sh”)相当于调用system(“/bin/sh”)。
1 | pwndbg> p (*(struct _IO_jump_t *)0x602460) |
libc2.24中的利用方式
在libc2.24中加入对vtable虚表指针的检查,函数IO_validate_vtable会检查vtable指针是否在合法的地址上,即是否在__libc_IO_vtables段中,如果不在合法的段中,会调用_IO_vtable_check()进行下一步的检查。如果不能通过检查就会引发abort。这使得上面修改vtable虚表指针的利用方式不再可行。
1 | //glibc-2.24 ./libio/libioP.h |
1 | //glibc-2.24 /libio/vtables.c |
虽然不能在__libc_IO_vtables段外伪造虚表指针,但是可以利用其他的不在检查范围内的虚表,正如很多大佬的博客中写的那样,利用_IO_str_jumps。
_IO_str_overflow
我这里调试的是CTF-WIKI中修改后的house of orange,首先是利用_IO_str_jumps->_IO_str_overflow。
首先看一下_IO_str_jumps的结构。
1 | //glibc-2.24 /libio/strops.c |
在函数_IO_str_overflow可以伪造劫持控制流:
1 | //glibc-2.24 /libio/strops.c |
涉及到的结构及变量有:
1 | //glibc-2.24 ./libio/strfile.h |
需要满足的条件有:
1 | 1.fp->_flags & _IO_NO_WRITES为假 |
对于第一个条件,可以设置如下:
1 | fp->_flags = 0 |
对于第二个条件,由于flush_only = EOF = 0,转化为pos = fp->_IO_write_ptr - fp->_IO_write_base,_IO_blen (fp) = fp->_IO_buf_end - fp->_IO_buf_base,
联系第四个条件,new_size指向bin_sh_addr,而new_size有如下等式:
1 | new_size = 2 * old_blen + 100 = 2 * _IO_blen (fp) + 100 = 2 * (fp->\_IO\_buf\_end - fp->\_IO\_buf\_base) + 100 = bin_sh_addr |
因此,可以构造:
1 | fp->\_IO\_buf\_base = 0 |
为了比较中的大于等于关系成立,可以尽心如下构造:
1 | fp->_IO_write_base = 0 |
对于第三个条件与第一个条件构造相同。
第四个条件在构造第二个条件时已经满足。
对于第五个条件,fp->_s._allocate_buffer要设置为system的地址,_s._allocate_buffer在fp中的偏移是0xe0,因此要设置:
1 | fp+0xe0 = system_addr |
最后我看到有博客里还设置了mode的值,我也没明白是为什么。
1 | fp->mode = 0 |
那程序如何调用_IO_str_overflow呢?同样在调用_IO_flush_all_lockp时调用_IO_OVERFLOW,在_IO_OVERFLOW中调用_IO_str_overflow。不知道这个地方怎么写….
总结构造的条件是:
1 | fp->_flags = 0 |
_IO_str_finish
在_IO_str_jumps libio_vtable中第二项是_IO_str_finish中同样有相对地址的引用,而且相对比较简单。
1 | //glibc-2.24 /libio/strops.c |
需要满足的条件有:
1 | fp->_IO_buf_base为真,(fp->_flags & _IO_USER_BUF)为假 |
可以构造以下条件以通过检查:
1 | fp->_flags = 0 |
参考
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/house_of_orange/
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unsorted_bin_attack/
http://tacxingxing.com/2018/01/10/house-of-orange/
https://dhavalkapil.com/blogs/FILE-Structure-Exploitation/
https://firmianay.gitbooks.io/ctf-all-in-one/doc/4.13_io_file.html