Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】CTF中带来的IO_FILE新思路

0
0
【技术分享】CTF中带来的IO_FILE新思路

2017-11-09 09:54:56

阅读:1226次
点赞(0)
收藏
来源: 安全客





【技术分享】CTF中带来的IO_FILE新思路

作者:FlappyPig





【技术分享】CTF中带来的IO_FILE新思路

作者:FlappyPig

预估稿费:300RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言

之前一直打算写一份有关IO_FILE的文章,但是由于自己太忙,所以一直都没有写。最初接触到IO_FILE是在pwnable.tw网站上做题时,碰到的题目能够通过对stdin进行伪造的话,能够把伪造虚表的地址以及虚表中的函数。从而造成任意代码执行。

之后再次遇见IO_FILE的时候是在2016的hitcon上面的house of orange,这个题目将IO_FILE和堆中的unsorted bin attack结合了起来。通过unsorted bin attack修改了IO_list_all,在flush io的时候造成了任意代码的执行。这样的攻击方法都是通过伪造IO_FILE的结构体,修改虚表的位置来利用的。

但是之后,由于ubuntu17.04的发布,libc-2.24.so中增加了对vtable的check,所以之前通过修改虚表来进行漏洞利用的方法现在已经用不了了。


新的思路

之后在参加WCTF 2016的时候,hitcon给出了challenge wannaheap。这个题目当时所有参赛的队伍没有一个做出来。之后在赛后分享的时候,根据它们的讲述,才发现这个题目为什么当时没有人解出来......它将IO_FILE的利用带上了一个新的层面。我们下面对这个题目详细分析一下来进一步加深对IO_FILE利用的理解,本文中对wannaheap的解题思路完全是根据当时hitcon在WCTF上分享的来的。


Wannaheap

题目的结构体只有两个,chunk和node

struct__attribute__((packed))__attribute__((aligned(4)))node { _QWORDleft; _QWORDright; __int64key; __int64data; _DWORDrandom; }; struct__attribute__((aligned(8)))chunk { _QWORDreal_addr; _QWORDaddr; };

首先它实现了一个前后都有随机数据和随机大小的堆分配结构,这样的话,堆的分配就变得难以预测。


【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路
分配的大小是rand1 + size + rand2,其中size是想要分配的大小。

所以这中间的这个堆块的位置是很难找到的,calloc设置的size大小也是很难控制的。


【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路
【技术分享】CTF中带来的IO_FILE新思路

分配的chunk就像上面这样,后面会把这里的地址存到一个global的数组里面去。

chunk的存储方式像是一个存在key值的红黑树,左子树的key值小于根节点,右子树的key值大于根节点。


【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路
另外还增加了一个random的操作来对树进行rotate,但是这里有点没有弄懂,这样的操作貌似会破坏树的结构...不过这个存储对后面做exploit不是很重要,可以先放在一边。

vuln

这个题目存在两个漏洞,第一个漏洞在set_data_area这个函数里面,是一个libc任意地址写一个\x00的字节,malloc的时候如果size比较大的时候会调用mmap,当malloc的size在0x30000之上的时候就会mmap在libc和bin之间


【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路
所以之后的g_buf_ptr[len_1] = 0会在libc的任意地址写上\x00
【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路
另外一个漏洞是在Allocate函数里面,在设置data的时候read了0x18个字节到栈上面,但是栈上面并没有被清干净,所以strdup之后data会被存到Nosql里面去。之后在Read函数里面就可以泄漏这个地址了。
【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路

利用

因为只能够往libc里面写一个\x00,这就很难去利用了...

但是在ubuntu 17.04里面这个利用是可能的,也只能够在ubuntu 17.04里面进行利用,只有在libc.so.2.14版本中才可以。

首先必须要认识的是stdin这个结构体。


【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路
这个结构体里面比较关键的就是IO_read_ptr, IO_read_end, IO_read_base, IO_write_base ...这些指针。他们是用来作为输入和输出的缓冲区的。从阅读源码中可以知道。

所以这里我们可以往IO_buf_base的最后一个字节写上\x00,这样IO_buf_base就正好指向_IO_buf_end的开头了,但是由于libc-2.24.so中的vtable check,我们无法伪造整个的vtable。

所以之后我们就往下覆盖直到main_arena,其中会经过malloc_hook,可以把malloc_hook覆盖成任意数值,但是这里用了seccomp,所以我们没有办法把它覆盖成onegadget的地址。

往下是main_arena的位置,我们可以覆盖到unsortedbin[1]的地址,覆盖成伪堆块的地址,然后在伪堆块的bk上面写上_dl_open_hook - 0x10的地址来做unsorted bin attack。这样的话unsortedbin attack 之后dl_open_hook的位置就会被写上main_arena + 88的地址,让dl_open_hook指向了main_arena+88。
【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路
dl_open_hook是一个结构体,这个结构体里面是三个函数的指针,当dl_open_hook不是空的时候会对vtable进行check,然后能够跳到dl_open_hook的dlopen_mode上面,具体什么原因我也不太清楚。
【技术分享】CTF中带来的IO_FILE新思路

所以这样我们就能够得到执行一次gadget的机会了。但是这里要怎么控制rip跳转到我们的rop上面呢?

通过 mov rdi, rax; call [rax + 0x20];这个gadget就能够控制rdi和rip。 然后通过setcontext+52
【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路

能够控制rsp跳到我们的libc area里面,这样的话就能够继续执行rop了。

这个题目的巧妙之处就在于它利用了libc-2.24.so中_IO_buf_end的偏移最后一个字节是\x00。所以这个\x00的写入才能够控制整个IO_FILE结构体。


另一种思路

在此之后,我在WHCTF 2017的线上赛中出了一道名叫stackoverflow的题目。不过很显然这个题目不会是一个简单的stackoverflow。

这个题目的思路与预期解法都是和wannaheap一样的,通过一个\x00字节的任意写,控制整个IO_FILE,然后得到任意代码执行的能了。

但是在比赛之后我审查他们提交的writeup的时候发现AAA的大佬们提供了另外一种不同的思路,同样是由于libc-2.24.so的vtable check的限制,他们给出了另外一种不同的绕过思路。


_IO_vtable_check到底做了什么

https://code.woboq.org/userspace/glibc/libio/vtables.c.html#39

主要检查了三种情况,如果这三种情况都不满足的话,那么就说明vtable已经被改变过了。所以之后就会跳到

__libc_fatal("Fatalerror:glibcdetectedaninvalidstdiohandle\n");

how to bypass the _IO_vtable_check

其实还是利用在

int _IO_flush_all_lockp(intdo_lock) { intresult=0; struct_IO_FILE*fp; intlast_stamp; #ifdef_IO_MTSAFE_IO __libc_cleanup_region_start(do_lock,flush_cleanup,NULL); if(do_lock) _IO_lock_lock(list_all_lock); #endif last_stamp=_IO_list_all_stamp; fp=(_IO_FILE*)_IO_list_all; while(fp!=NULL) { run_fp=fp; if(do_lock) _IO_flockfile(fp); if(((fp->_mode<=0&&fp->_IO_write_ptr>fp->_IO_write_base) #ifdefined_LIBC||defined_GLIBCPP_USE_WCHAR_T ||(_IO_vtable_offset(fp)==0 &&fp->_mode>0&&(fp->_wide_data->_IO_write_ptr >fp->_wide_data->_IO_write_base)) #endif ) &&_IO_OVERFLOW(fp,EOF)==EOF) result=EOF; if(do_lock) _IO_funlockfile(fp); run_fp=NULL; if(last_stamp!=_IO_list_all_stamp) { /*Somethingwasaddedtothelist.Startalloveragain.*/ fp=(_IO_FILE*)_IO_list_all; last_stamp=_IO_list_all_stamp; } else fp=fp->_chain; } #ifdef_IO_MTSAFE_IO if(do_lock) _IO_lock_unlock(list_all_lock); __libc_cleanup_region_end(0); #endif returnresult; }

中的chain字段,伪造一个file结构体,然后修改chain为这个结构体的地址。之后在调用IO_flush_all_lockp函数的时候,这个结构体就会被调用。

但是因为check了vtables,所以不能够任意提供一个伪造的vtable的地址,但是可以使用io_str_jumps 这个vtable。

正常的IO_FILE_jumps的IO_OVERFLOW就是直接跳到虚表中这个偏移位置的地址

#defineJUMP1(FUNC,THIS,X1)(_IO_JUMPS_FUNC(THIS)->FUNC)(THIS,X1)

但是在io_str_jumps的虚表中 overflow 对应的处理函数是不一样的,我们可以利用这里不一样的地方直接执行system('/bin/sh')

https://code.woboq.org/userspace/glibc/libio/strops.c.html#81
【技术分享】CTF中带来的IO_FILE新思路

【技术分享】CTF中带来的IO_FILE新思路

最终通过

(*((_IO_strfile*)fp)->_s._allocate_buffer)(new_size);

跳到system('/bin/sh')上面


总结

文章介绍了通过IO_FILE来造成任意代码执行的方法。IO_FILE从开始的没有任何保护,到现在有了对结构体关键位置的check。漏洞的利用方式也开始变的越来越复杂,对利用的要求也会越来越高。不过在我看来,只要能够做到对IO_FILE的改写,就能够做到任意代码执行。

如果文章中有什么说的不对的地方,还请大家多多批评指正。



【技术分享】CTF中带来的IO_FILE新思路
【技术分享】CTF中带来的IO_FILE新思路
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4661.html

Viewing all articles
Browse latest Browse all 12749

Latest Images

Trending Articles





Latest Images