个人备案网站可以做新闻站吗,哪个网站可以做投资回测,wordpress建设企业官网,招人在哪里找最快简介最近学习了一下_IO_FILE的利用#xff0c;刚好在pwnable.tw上碰到一道相关的题目。拿来做了一下#xff0c;遇到了一些困难#xff0c;不过顺利解决了#xff0c;顺便读了一波相关源码#xff0c;对_IO_FILE有了更深的理解。文章分为三部分#xff0c;分别是利用原理…简介最近学习了一下_IO_FILE的利用刚好在pwnable.tw上碰到一道相关的题目。拿来做了一下遇到了一些困难不过顺利解决了顺便读了一波相关源码对_IO_FILE有了更深的理解。文章分为三部分分别是利用原理、实例和源码阅读。源码部分比较无聊所以我把它放在了最后。原理原理我们使用fopen打开一个文件会在堆上分配一块内存区域用来存储FILE结构体存储的结构体包含两个部分前一部分为_IO_FILE结构体file后一部分是一个指向struct IO_jump_t的指针vtable 这个结构体种存储着一系列与文件IO相关的函数指针。在我们调用fclose关闭一个文件时我们最终会调用到vtable中存储的函数指针。如果我们能够将vtable中的指针替换为我们自己想要跳转到的地址就可以劫持程序流程。利用前提本文仅考虑libc版本 2.23的情况。因为大于等于2.24的libc会对vtable的位置做判断无法令其指向自己构造的区域可以控制vtable指针或者fp指针指向的位置有一块已知地址的可控内存区域大小需要视情况而定利用方式1直接覆盖vtable指针这个没什么好说的将vtable指针指向可控内存将__finish(off2*SIZE_T)构造为要执行的地址即可利用方式2覆盖fp指针有的时候我们无法直接控制FILE结构体的vtable指针但是我们可以控制文件指针。因此我们需要伪造整个FILE结构体然后控制vtable指针指向我们自己构造的函数列表在__finish(off2*SIZE_T)位置布置好我们想要调用的地址最后调用fclose。这种方式的 关键 在于要伪造一个合适的FILE结构体使得在fclose的过程中不会触发异常造成程序异常终止。为了避免这种情况一种最简单的方式就是将FILE结构体的_flags变量的_IO_IS_FILEBUF标志位置0。例如置为0xffffdfff。这样做的主要原因是为了绕过一些操作。if (fp-_IO_file_flags _IO_IS_FILEBUF)_IO_un_link ((struct _IO_FILE_plus *) fp);_IO_acquire_lock (fp);if (fp-_IO_file_flags _IO_IS_FILEBUF)status _IO_file_close_it (fp);elsestatus fp-_flags _IO_ERR_SEEN ? -1 : 0;_IO_release_lock (fp);_IO_FINISH (fp);相关代码如上。可以看到当_IO_IS_FILEBUF位为0时函数不会执行_IO_un_link和_IO_file_close_it函数而直接执行_IO_FINISH函数。在_IO_FINISH函数中会直接调用vtable中的__finish函数。其中_IO_IS_FILEBUF被定义为0x2000。#define _IO_IS_FILEBUF 0x2000利用实例题目简介测试用的题目来源于pwnable.tw题目名为seethefile。为FILE结构体利用的一道比较经典的题目。在这里只谈一下解题思路不给出exp。逆向分析概要openfile打开一个文件文件名由用户输入但是当文件名含flag时会退出程序readfile将文件中的内容读入一个全局字符数组中writefile将全局字符数组中的内容输出到屏幕上closefile关闭文件当输入为5时程序要求输入一个名字然后关闭存储文件指针fp退出程序main.png以上步骤的文件指针都存放在一个全局变量中bss对应的结构如下bss.png漏洞分析由逆向结果可知。在读取数字时存在栈溢出但是程序开启了栈保护所以不可利用当输入命令5读取用户指令时存在一个bss段的溢出利用这个溢出我们可以覆盖fp的指针指向我们想要的位置同时可以伪造FILE结构体。利用fclose来实现攻击。漏洞利用leak libc题目给出了libc的文件为了执行libc中的system命令还需要获取libc加载的基址。我们可以通过打开/proc/self/mmap这个虚拟文件来获取当前进程的地址空间情况。获得到libc的加载基址后就可以计算出libc中system的偏移。接下来我们就可以利用_IO_FILE结构体进行攻击。构造FILE构造FILE结构体只需要关注两个变量第一个为FILE结构体的_flags字段只需要_flags 0x2000为0就会直接调用_IO_FINSH(fp)_IO_FINISH(fp)相当于调用fp-vtabl-__finish(fp)。将fp指向一块内存PP偏移0的前4字节设置为0xffffdfffP偏移4位置放上要执行的字符串指令(字符串以;开头即可)P偏移sizeof(_IO_FILE)大小位置(vtable)覆盖为内存区域QQ偏移2*4字节处(vtable-__finish)覆盖为system函数地址即可。glibc fclose源码学习glibc的版本为2.23.90复制粘贴比较多主要是为了方便查阅。以下内容对大部分的fclose函数进行了层层解剖很多部分与漏洞利用无太大关系按需取用。_IO_FILE与_IO_FILE_plus结构体在阅读fclose前先来了解一些有关于FILE结构体的知识。在C语言中成功调用fopen函数后会在堆上分配一块空间用于存放_IO_FILE_plus结构体并且返回结构体的首地址。阅读源码可以发现_IO_FILE_plus结构体只是在_IO_FILE结构体后添加了一个虚表指针 vtable。/* _IO_FILE_plus结构体 *//* in libio/libioP.h */struct _IO_FILE_plus{_IO_FILE file;const struct _IO_jump_t *vtable;};虚表指针指向了如下的一个结构体。JUMP_FIELD是一个接收两个参数的宏前一个参数为类型名后一个为变量名。结构体的前两个变量实际上不会被使用到所以默认为0其余的变量存储着不同的函数指针在使用FILE结构体进行IO操作的过程中会通过这些函数指针调用到对应的函数。/*_IO_jump_t虚表结构体*//* in libio/libioP.h */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);/* 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 0get_column;set_column;#endif};_IO_FILE结构体的定义如下。__flags FILE结构体的一些状态_markers为指向markers结构体的指针变量为一个单向链表结构存放流的位置_chain变量为一个链表的指针进程中创建的FILE结构体会通过这个变量连成一个单向链表另一点需要注意的是在新版本中_IO_FILE_complete结构体被删除其中的字段被添加到_IO_FILE结构体中/*_IO_FILE结构体*//* libio/libio.h */struct _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 putbackget 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 0int _blksize;#elseint _flags2;#endif_IO_off_t _old_offset; /* This used to be _offset but its too small. */#define __HAVE_COLUMN /* temporary *//* 1column 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;# elsevoid *__pad1;void *__pad2;void *__pad3;void *__pad4;# endifsize_t __pad5;int _mode;/* Make sure we dont get into trouble again. */char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];#endif};fclose代码为了精简我删掉了一部分关系不大的代码下面的函数是新版本的fclose代码IO_old_fclose主要代码与新版本类似/* libio/iofclose.c */int_IO_new_fclose (_IO_FILE *fp){int status;/*这里本来有个对版本进行检测的代码根据FILE结构中_vtable_offset变量是否为0来判断不为0则执行_IO_old_fclose*//* First unlink the stream. */if (fp-_IO_file_flags _IO_IS_FILEBUF)_IO_un_link ((struct _IO_FILE_plus *) fp);_IO_acquire_lock (fp);if (fp-_IO_file_flags _IO_IS_FILEBUF)status _IO_file_close_it (fp);elsestatus fp-_flags _IO_ERR_SEEN ? -1 : 0;_IO_release_lock (fp);_IO_FINISH (fp);if (fp-_mode 0){#if _LIBC/* This stream has a wide orientation. This means we have to freethe conversion functions. */struct _IO_codecvt *cc fp-_codecvt;__libc_lock_lock (__gconv_lock);__gconv_release_step (cc-__cd_in.__cd.__steps);__gconv_release_step (cc-__cd_out.__cd.__steps);__libc_lock_unlock (__gconv_lock);#endif}else{if (_IO_have_backup (fp))_IO_free_backup_area (fp);}if (fp ! _IO_stdin fp ! _IO_stdout fp ! _IO_stderr){fp-_IO_file_flags 0;free(fp);}return status;}该函数的流程可以粗略地进行如下表示fclose flowchart.png流程图中有几个关键函数,至于加解锁什么的我忽略了_IO_un_link_IO_file_close_it_IO_FINISH_IO_free_backup_areafree在检查vtable_offset0之后函数对fp-_flags的_IO_IS_FILEBUF位进行检查_IO_IS_FILEBUF定义如下#define _IO_IS_FILEBUF 0x2000若该位不为0则调用_IO_un_link(fp)将fp指向的FILE结构体从_IO_list_all的单向链表中取下并调用_IO_file_close_it(fp)关闭fp。然后将调用_IO_FINISH(fp)相当于执行((struct IO_FILE_plus *)fp-vtable)-__finish(fp)。_IO_un_link/* in libio/genops.c */void_IO_un_link (struct _IO_FILE_plus *fp){if (fp-file._flags _IO_LINKED){struct _IO_FILE **f;#ifdef _IO_MTSAFE_IO_IO_cleanup_region_start_noarg (flush_cleanup);_IO_lock_lock (list_all_lock);run_fp (_IO_FILE *) fp;_IO_flockfile ((_IO_FILE *) fp);#endifif (_IO_list_all NULL);else if (fp _IO_list_all){_IO_list_all (struct _IO_FILE_plus *) _IO_list_all-file._chain;_IO_list_all_stamp;}elsefor (f _IO_list_all-file._chain; *f; f (*f)-_chain)if (*f (_IO_FILE *) fp){*f fp-file._chain;_IO_list_all_stamp;break;}fp-file._flags ~_IO_LINKED;#ifdef _IO_MTSAFE_IO_IO_funlockfile ((_IO_FILE *) fp);run_fp NULL;_IO_lock_unlock (list_all_lock);_IO_cleanup_region_end (0);#endif}}_IO_un_link首先判断fp的标志位中的_IO_LINKED是否置位若置位进行下一步操作最后将其清零#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/若_IO_list_all ! fp则_IO_un_link函数将从_IO_list_all开始遍历链表寻找fp指针找到后将其前一个节点指针指向后一个节点指针即指向fp-file._chain若_IO_list_allfp则将全局变量_IO_list_all的值更改为IO_list_all-file._chain。_IO_file_close_it/* in libio/fileops.c *//* 在新版本中 _IO_file_close_it被定义为_IO_new_file_close_it */int_IO_new_file_close_it (_IO_FILE *fp){int write_status;if (!_IO_file_is_open (fp))return EOF;if ((fp-_flags _IO_NO_WRITES) 0 (fp-_flags _IO_CURRENTLY_PUTTING) ! 0)write_status _IO_do_flush (fp);elsewrite_status 0;_IO_unsave_markers (fp);int close_status ((fp-_flags2 _IO_FLAGS2_NOCLOSE) 0? _IO_SYSCLOSE (fp) : 0);/* Free buffer. */#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_Tif (fp-_mode 0){if (_IO_have_wbackup (fp))_IO_free_wbackup_area (fp);_IO_wsetb (fp, NULL, NULL, 0);_IO_wsetg (fp, NULL, NULL, NULL);_IO_wsetp (fp, NULL, NULL);}#endif_IO_setb (fp, NULL, NULL, 0);_IO_setg (fp, NULL, NULL, NULL);_IO_setp (fp, NULL, NULL);_IO_un_link ((struct _IO_FILE_plus *) fp);fp-_flags _IO_MAGIC|CLOSED_FILEBUF_FLAGS;fp-_fileno -1;fp-_offset _IO_pos_BAD;return close_status ? close_status : write_status;}_IO_new_file_close_it首先根据fp-_fileno是否为0判断文件是否打开#define _IO_file_is_open(__fp) ((__fp)-_fileno ! -1)若文件未打开则直接返回EOF。否则函数将继续执行if ((fp-_flags _IO_NO_WRITES) 0 (fp-_flags _IO_CURRENTLY_PUTTING) ! 0)write_status _IO_do_flush (fp);以上代码将fp中未输出的部分输出_IO_do_flush(fp)定义如下#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T# define _IO_do_flush(_f) \((_f)-_mode 0 \? _IO_do_write(_f, (_f)-_IO_write_base, \(_f)-_IO_write_ptr-(_f)-_IO_write_base) \: _IO_wdo_write(_f, (_f)-_wide_data-_IO_write_base, \((_f)-_wide_data-_IO_write_ptr \- (_f)-_wide_data-_IO_write_base)))#else# define _IO_do_flush(_f) \_IO_do_write(_f, (_f)-_IO_write_base, \(_f)-_IO_write_ptr-(_f)-_IO_write_base)#endif不做过多解释。然后fclose将调用_IO_unsave_markers(fp)将保存的markers清除在这个版本的libc代码中这个函数有一部分功能还没完成用(#define TODO围着)唯一值得注意的是函数最后if (_IO_have_backup (fp))_IO_free_backup_area (fp);void_IO_free_backup_area (_IO_FILE *fp){if (_IO_in_backup (fp))_IO_switch_to_main_get_area (fp); /* Just in case. */free (fp-_IO_save_base);fp-_IO_save_base NULL;fp-_IO_save_end NULL;fp-_IO_backup_base NULL;}如果fp-_IO_save_base不为空它将被free。之后在_IO_new_file_close_it中执行了int close_status ((fp-_flags2 _IO_FLAGS2_NOCLOSE) 0? _IO_SYSCLOSE (fp) : 0);当fp-_flags2的_IO_FLAGS2_NOCLOSE没有被置位时会调用_IO_SYSCLOSE(fp)相当于调用_IO_FILE_plus结构体中的vtable中的__close函数。这一次调用_IO_un_link好像并没有实际作用最后又调用了_IO_un_link(fp)并设置了一些flags_IO_un_link ((struct _IO_FILE_plus *) fp);fp-_flags _IO_MAGIC|CLOSED_FILEBUF_FLAGS;fp-_fileno -1;fp-_offset _IO_pos_BAD;_IO_have_backup上面已经提到了略略略free(fp)用户打开的FILE结构体是分配在堆上的在fclose中最终会被free释放。至此glibc的fclose源代码分析完毕。