重新分析:
主要问题在于memcpy拷贝到stdin中,stdin是什么
// libio/libio.hstruct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */#define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno;#if 0 int _blksize;#else int _flags2;#endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _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 /* Wide character stream stuff. */ 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; /* Make sure we don't get into trouble again. */ char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];#endif};extern struct _IO_FILE_plus _IO_2_1_stdin_;//只要存在FILE 必然会有这三个结构体extern struct _IO_FILE_plus _IO_2_1_stdout_;extern struct _IO_FILE_plus _IO_2_1_stderr_;
/* Extra data for wide character streams. */struct _IO_wide_data{ wchar_t *_IO_read_ptr; /* Current read pointer */ wchar_t *_IO_read_end; /* End of get area. */ wchar_t *_IO_read_base; /* Start of putback+get area. */ wchar_t *_IO_write_base; /* Start of put area. */ wchar_t *_IO_write_ptr; /* Current put pointer. */ wchar_t *_IO_write_end; /* End of put area. */ wchar_t *_IO_buf_base; /* Start of reserve area. */ wchar_t *_IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */ wchar_t *_IO_backup_base; /* Pointer to first valid character of backup area */ wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */ __mbstate_t _IO_state; __mbstate_t _IO_last_state; struct _IO_codecvt _codecvt; wchar_t _shortbuf[1]; const struct _IO_jump_t *_wide_vtable;};
stdin stdout stderr 三个是分别指向三个结构体的指针;即:
_IO_2_1_stderr__IO_2_1_stdout__IO_2_1_stdin_
// libio/libioP.hstruct _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); /* showmany */ 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};/* We always allocate an extra word following an _IO_FILE. This contains a pointer to the function jump table used. This is for compatibility with C++ streambuf; the word can be used to smash to a pointer to a virtual function table. */struct _IO_FILE_plus{ _IO_FILE file; const struct _IO_jump_t *vtable;};
FILE结构通过chain域形成链条,链表头部用_IO_list_all表示
通过伪造结构体,更改vtable指针 vtable指针指向_IO_jump_t结构体
保存一个虚表,记录实际调用函数地址
FILE结构体
源码真好看也是真恶心
https://www.bookstack.cn/read/CTF-All-In-One/doc-4.13_io_file.md#FILE%20%E7%BB%93%E6%9E%84
fread()函数,实现
// libio/iofread.c_IO_size_t_IO_fread (void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp){ _IO_size_t bytes_requested = size * count; _IO_size_t bytes_read; CHECK_FILE (fp, 0); if (bytes_requested == 0) return 0; _IO_acquire_lock (fp); bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested); // 调用 _IO_sgetn 函数 _IO_release_lock (fp); return bytes_requested == bytes_read ? count : bytes_read / size;}
函数通过调用_IO_sgetn实现
_IO_sgetn
// libio/genops.c_IO_size_t_IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n){ /* FIXME handle putback buffer here! */ return _IO_XSGETN (fp, data, n); // 调用宏 _IO_XSGETN???}
e... 再调用宏????
// libio/libioP.h#define _IO_JUMPS_FILE_plus(THIS) \ _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)#if _IO_JUMPS_OFFSET# define _IO_JUMPS_FUNC(THIS) \ (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \ + (THIS)->_vtable_offset))# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset#else# define _IO_JUMPS_FUNC(THIS) _IO_JUMPS_FILE_plus (THIS)# define _IO_vtable_offset(THIS) 0#endif#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
哦,这个熟,在vtable指向的那个表_IO_jump_t里有这个东东也有JUMP的类似函数,最终调用函数;那个函数就不写了;
类似的fwrite也是如此;
--------------
大概清楚IO的实现流程,看看攻击方式:
FSOP:
通过劫持_IO_list_all_,伪造链表,在检测到内存错误,执行exit ,main函数返回时执行_IO_flush_all_lockp() 从而getshell
shit。。。
// libio/genops.cint_IO_flush_all_lockp (int do_lock){ int result = 0; struct _IO_FILE *fp; int last_stamp;#ifdef _IO_MTSAFE_IO __libc_cleanup_region_start (do_lock, flush_cleanup, NULL); if (do_lock) _IO_lock_lock (list_all_lock);#endif last_stamp = _IO_list_all_stamp; fp = (_IO_FILE *) _IO_list_all; // 将其覆盖为伪造的链表 while (fp != NULL) { run_fp = fp; if (do_lock) _IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) // 条件#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))#endif ) && _IO_OVERFLOW (fp, EOF) == EOF) // fp 指向伪造的 vtable result = EOF; if (do_lock) _IO_funlockfile (fp); run_fp = NULL; if (last_stamp != _IO_list_all_stamp) { /* Something was added to the list. Start all over again. */ fp = (_IO_FILE *) _IO_list_all; last_stamp = _IO_list_all_stamp; } else fp = fp->_chain; // 指向下一个 IO_FILE 对象 }#ifdef _IO_MTSAFE_IO if (do_lock) _IO_lock_unlock (list_all_lock); __libc_cleanup_region_end (0);#endif return result;}
// libio/libioP.h#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
在以上三种情况下函数调用流程
malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW
libc-2.24后更新防御机制
所有的vtable放入一个单独的段,任何间接跳转时检查 vtable是否在段中,如果不在则_IO_vtable_check一下,确认是否在必要时终止进程;
即不能通过改写v伪造vtable
额,大概这样,更深一步具体认识应该在wp出来后再看。