首页
社区
课程
招聘
[原创]IOFILE exploit入门
发表于: 2023-9-3 21:52 11581

[原创]IOFILE exploit入门

2023-9-3 21:52
11581

前段时间给人讲课,碰上了高版本glibc堆利用这部分。刚好自己啥也不会,于是花了些时间好好学了下IOFILE在高版本下的利用。好在最后没有翻车,自己在备课期间也独立完成了一些经典例题,算是有了一些粗浅的收获。现以一个初学者的目光重新梳理学习IOFILE利用的整个过程。

IOFILE的利用,通常需要伪造IOFILE结构体,随后通过一些方式触发IO流,使得程序调用我们劫持后的IO函数,从而达成getshell或者orw。所以IOFILE exploit实际上是整个利用环节的最后一步,要达成伪造IOFILE结构体、触发IO链,往往需要借助一些辅助手法

在高版本下,最为常用的是largebin attack,该手法需要一个UAF,达成后能在任意目标地址写上堆地址,简单暴力,用过都说好。另一种是tcache stashing unlink,该手法在house of pig中被使用,要求程序使用calloc,并存在UAF,通常结合largebin attack达成将目标地址分配为chunk。

原先存在两条利用链,其中一条在高版本下已被封禁,查找资料时需甄别。

首先我们需要了解largebin的结构:

一个large bin中包含一定范围大小的large chunk,如第一个largebin大小范围为(0x400-0x430),第二个为(0x440-0x470)。同一个bin中,相同大小的chunk之间用fdbk相连,不同大小用fd_nextsizebk_nextsize相连,且自动由大到小排序,如上图中左边的大于右边。

当有新的chunk加入largebin,且大小小于该bin中已有的chunk时,会进行如下操作(精简版):

该操作实际上完成了一个简单的将chunk加入双向链表的过程,可以逐步手动模拟加深理解

该操作缺失对双向链表完整性的检查,可以加以利用

我们假设largebin中仅存在chunk0,现在准备将chunk1加入largebin,且chunk0 > chunk1。我们把chunk0的bk_nextsize修改为[target-0x20](即target目标地址位于以[target-0x20]开头的chunk的fd_nextsize处)

修改完成后,我们将chunk1入链,重点关注上述代码片段中注释的部分。critical1处 victim->bk_nextsize = (&target)-4),critical2处 (&target)-4)->fd_nextsize = victim,即*target = victim。目标达成

由此可见,largebin attack的利用相当简单,只需一个UAF即可完成

glibc加入tcache后,当从smallbin中取出chunk时,如果smallbin中仍有chunk且对应tcache未填满,则会将smallbin中剩余chunk全部依次放入对应tcache中。而在该过程中,只对从smallbin中取出的chunk进行了完整性检查,后续chunk均没有。该手法则利用了这一漏洞。

以下为将smallbin中chunk放入tcache的代码片段:

假设smallbin中只存在两个chunk,先入的为chunk0,后入的为chunk1,根据smallbin的FIFO原则,取出时先取出chunk0

同时令对应大小的tcache中已填入5个chunk

此时将chunk1的bk位修改为目标地址target(不破坏fd),并利用其他手法使得[target+0x8]处为一个指向可写内存的指针。把target看作一个chunk的用户指针,则[target+0x8]bk位。此处令其可写是因为进行该攻击时会向该指针指向的地址处写一个值,如果不可写的话程序会报错。用largebin attack将此处覆盖为可写的堆地址是一种常见操作

此时,我们用calloc函数将chunk0申请出来。calloc具有跳过tcache申请chunk的性质,所以此处只能用calloc。之后chunk1会加入tcache,chunk1的bk指向的target也会加入tcache。如果我们使用malloc,根据tcache LIFO的性质,就能将target申请出来。

程序在进行IO操作时,会用到IOFILE相关的结构。例如众所周知的stdinstdoutstderr,都是指向IO结构体的指针。当打开一个文件时,程序需要记录文件的输入输出缓冲区等内容,于是需要一个_IO_FILE结构体进行记录。除此之外,对文件进行的各种操作形成了一张张函数虚表,文件需要一个指针记录对应的函数虚表,该指针被定义为_IO_jump_t类型。_IO_FILE 结构体和虚表指针_IO_jump_t形成了一个称为_IO_FILE_plus的结构体,它的定义是这样的:

常见的_IO_FILE_plus结构体有_IO_2_1_stderr_IO_2_1_stdout_IO_2_1_stdin,前面提到的stdin等就是指向这些对应_IO_FILE_plus结构体的指针

可以通过gdb的p指令查看

此外,存在一个指向_IO_FILE_plus结构体的_IO_list_all指针,通常情况下指向_IO_2_1_stderr

每个_IO_FILE结构体中都存在一个_chain指针,指向下一个_IO_FILE_plus结构体。通常情况,表示如下:

当有新文件被打开时,对应的_IO_FILE_plus结构体就会被插入到链首

以下是_IO_FILE结构体的源码,看起来可能有些头大,重点需要了解的是几个缓冲区的指针_chain

_IO_FILE_complete可以理解为_IO_FILE的豪华加长版,其中的_wide_data需要关注一下,有些手法会用到。

vtable是一个指向_IO_jump_t结构体的指针。当打开一个文件时,相应的 _IO_FILE 结构体会被创建,并将其 vtable 字段指向对应文件类型的 _IO_jump_t 结构体的地址。这样,当需要执行文件操作时,可以通过 _IO_FILE 结构体中的 vtable 字段获取到相应的 _IO_jump_t 结构体,并通过其中的函数指针调用相应的文件操作函数。

通常,一个_IO_jump_t 结构体包含以下函数指针

注意,程序中存在很多_IO_jump_t结构体,不同的_IO_FILE_plus可能采用不同的_IO_jump_t,例如stdin/stdout/stderr使用_IO_file_jumps,可以通过gdb查看

FSOP(File Stream Oriented Programming是针对_IO_FILE结构体与IO操作的攻击方式

通常可以分为两个步骤:劫持伪造IO结构体、触发IO流

因为触发IO流的方式影响程序进入的IO结构体,所以我们先讲如何触发IO流

触发IO流的方式主要分为两种:FSOP的经典方式和house of kiwi的方式

该函数的调用有以下三种触发方式:

可以看到,高版本下,基本的触发方式都是通过exit函数,其栈回溯为:

就能调用到_IO_flush_all_lockp()函数

该函数的代码片段如下:

可以看到,在经历一系列条件判断后,程序会调用虚表中的_IO_OVERFLOW 函数,而虚表指针是可以由我们伪造的。所以合理布局,触发该函数,就能进入我们伪造的执行流。

下面讲如何达成调用_IO_OVERFLOW函数的条件:

根据短路原则,执行_IO_OVERFLOW需要满足前面的条件为真,这里有一个或语句,故有两种达成条件,满足任一即可:

这是较为常用的达成条件,只需伪造_IO_FILE中的_IO_write_ptr_IO_write_base_mode即可达成

该条件可以结合house of cat使用。其中_IO_vtable_offset (fp) == 0_vtable_offset为0,另外需要关注_wide_data结构体,该手法会在house of cat中讲解

条件满足后,会从_IO_list_all开始沿着fp -> chain遍历FILE结构体,执行对应虚表中的_IO_OVERFLOW,即<_IO_file_jumps+24>

该手法用于在程序无exit时触发IO,且能控制rdx

以下为__malloc_assert的代码片段

该函数调用的__fxprintffflush都能调用虚表中函数,有劫持的可能。

以下是进入__malloc_assert的方法

不满足以下条件任一即可进入:

通常用的比较多的是第三种,因为页对齐条件较苛刻,通常修改topchunk的size都能不对齐。注意不能改的太大,否则会进入别的assert。这里修改的方式一般是构造堆重叠largebin attack

接下来讲函数的调用链,这里分__fxprintffflush两种方式

调用链为

__fxprintf -> __vfxprintf -> locked_vfxprintf -> __vfprintf_internal -> _IO_new_file_xsputn

需要满足的条件为:

调用后,仅刷新stderr,调用虚表_IO_file_jumps中的_IO_new_file_xsputn,即<_IO_file_jumps+56>

fflush(stderr) => _IO_file_jumps => sync

用的比较少,没什么条件。但是因为在__fxprinf之后,所以需要保证__fxprinf不会挂掉。

好处是通过这条调用链,在调用sync时的rdx固定,都是IO_helper_jumps,可以结合后面讲到的<setcontext+61>利用。不过高版本中加入虚表检测,这种利用变得困难

如何让我们伪造的FILE结构体在触发IO时被刷新?

如果使用FSOP的方式,程序从_IO_list_all开始沿着fp -> chain遍历。故我们可以修改_IO_list_all指针,指向我们伪造的结构体,如果使用largebin attack就可以一步到位指向布置在chunk上的伪造结构体。同时还可以修改_chain指针,劫持到多个伪造的结构体的利用链

如果使用house of kiwi的方式,程序仅刷新stderr,可以修改stderr指针(如果可写),甚至直接修改_IO_2_1_stderr的内容

伪造FILE的手法繁多,这里举出最原始的一种手法作为例子

因为触发FSOP时,会调用虚表中<_IO_file_jumps+24>处的__overflow函数,我们只需把该位置填入system的地址。当然,虚表不可写,我们可以将vtable处改为我们伪造的虚表。同时此时的rdi指向FILE结构体的_flags,所以可以将该位置填上/bin/sh

构造方式如下:

干净又卫生

加入了对虚表的检查IO_validate_vtable()IO_vtable_check(),若无法通过检查,则会报错:Fatal error: glibc detected an invalid stdio handle

glibc中有一段完整的内存存放着各个vtable,其中__start___libc_IO_vtables指向第一个vtable地址_IO_helper_jumps,而__stop___libc_IO_vtables指向最后一个vtable _IO_str_chk_jumps结束的地址。若指针不在glibc的vtable段,会调用_IO_vtable_check()做进一步检查,以判断程序是否使用了外部合法的vtable(重构或是动态链接库中的vtable),如果不是则报错。

2.37以前有以下虚表

从此,虚表指针不能随意伪造了。好在这个检测依旧比较宽泛,我们依旧可以修改vtable为虚表内的其他指针,通过一定的偏移调用其他虚表内的函数

例如,原本house of kiwi会触发<_IO_file_jumps+56>处的_IO_new_file_xsputn函数,我们希望调用<_IO_cookie_jumps+120>处的_IO_cookie_write函数。原本vtable指向_IO_file_jumps,调用函数时则会将指针加上56。我们把vtable设置为<_IO_cookie_jumps+64>,那么在实际调用时,就会调用到<_IO_cookie_jumps+64+56>处的目标函数。这就是虚表偏移的思想,将执行流劫持到已有的函数上。

高版本下的IOFILE利用方式很多,但大多不外乎都是利用虚表偏移的思想伪造FILE结构体,并通过以上的方式触发IO,从而getshell或者orw

这里介绍几种我会的比较好用的

相较于他的实战意义,kiwi更大的价值可能在于它的两条思路,一条是上文提到的__malloc_assert触发IO,另一条则是setcontext<+61>

这是一个函数的片段,兼具布置寄存器、栈迁移的功能,常常在沙盒限制execve的情况下用来打orw链,汇编代码如下

围绕rdx进行参数的布置,就能设置好各个寄存器

这里最重要的两条是

[rdx+0xa0] => rsp

[rdx+0xa8] => rcx => ret

我们通常将rcx布置为ret的gadget,这样在最后ret的时候就能实现栈迁移到赋给rsp的地址上

可以理解为kiwi衍生出的攻击手法,需要以下三个条件

可以任意写一个可控地址(LargeBin Attack、Tcache Stashing Unlink Attack...)

一次任意地址读 或 再来一次任意写

可以触发 IO 流(FSOP、house of kiwi)

利用的虚表为_IO_cookie_jumps,存在拓展结构_IO_cookie_file(还是_IO_FILE_plus加长版),多了一个函数表,存在劫持的可能

需要调用的虚表函数如下

这里写了4个函数,其实大同小异,都调用了一个从_IO_cookie_file结构体的函数表中取出的函数指针,以_IO_cookie_write为例

所以我们只需伪造_IO_cookie_file结构体的函数表,就能调用任意函数。同时这里的rdi是可控的。如果要打getshell,可以直接构造;如果要打orw,可以利用libc中一个gadgets

完成从可控rdi到可控rdx的转换

此外,_IO_cookie_file里的函数指针不能直接伪造,因为默认开启了PTR_DEMANGLE (指针保护)

__pointer_chk_guard存在于TLS段上,将其ROR移位0x11后再与存入的地址进行异或

fs[0x30]的值位于与libc相邻的ld空间中,这个位置距离libc地址的偏移固定,可能与本地patchelf后不一致,可以通过爆破得到。因为偏移的变化值往往在地址末尾的第四个、第五个数(末三位不变),可编写如下脚本

详细题解可以看另一篇博客

用于程序中只有calloc而没有malloc的情况

通常需要一次largebin attack以完成tcache stashing unlink attack的准备

需要一次tcache stashing unlink attack

调用虚表_IO_str_jumps -> _IO_str_overflow函数

打tcache stashing没有malloc取不出chunk怎么办?

我有_IO_str_overflow!!!(自豪)

这个函数中,完美达成mallocmemcpyfree一条龙服务

什么?你的版本太高打不了free_hook

没关系,最后还有一个memset可以写libc中的got表

如果需要打orw,rdx也是可控的

<+53>mov rdx, QWORD PTR [rdi+0x28]

rdi指向_flagsrdi+0x28指向_IO_write_ptr,可控

详细题解可以看另一篇博客

只需要一次任意写!

调用虚表_IO_wfile_jumps中的_IO_wfile_seekoff函数

最终目的是调用_IO_switch_to_wget_mode函数

这里call了一个rax相关的地址,而raxrdi决定

再说调用条件,需要满足两个

was_writing == 1

即fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

这里用到了前面提到的_wide_data跳表,可以直接伪造so easy

mode!=0

rcx=>mode

如果无法满足,可以再做一个FILE结构体,进入第二个结构体时就置好了,详细内容参考Photon的博客

构造挺麻烦的,理解之后可以直接上别人的模板

详细题解可以看另一篇博客

victim_index = largebin_index (size);
/*查找该size属于的bin范围,返回index*/
bck = bin_at (av, victim_index);
/*取得bin*/
fwd = bck->fd;
 
if ((unsigned long) (size)< (unsigned long) chunksize_nomask (bck->bk))
{
    fwd = bck;
    bck = bck->bk;
    victim->fd_nextsize = fwd->fd;
    victim->bk_nextsize = fwd->fd->bk_nextsize;//critical1!!!
    fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//critical2!!!
}
victim_index = largebin_index (size);
/*查找该size属于的bin范围,返回index*/
bck = bin_at (av, victim_index);
/*取得bin*/
fwd = bck->fd;
 
if ((unsigned long) (size)< (unsigned long) chunksize_nomask (bck->bk))
{
    fwd = bck;
    bck = bck->bk;
    victim->fd_nextsize = fwd->fd;
    victim->bk_nextsize = fwd->fd->bk_nextsize;//critical1!!!
    fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//critical2!!!
}
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
        malloc_printerr ("malloc(): smallbin double linked list corrupted");
//只对第一个chunk进行了完整性检查,后面进入循环,无检查
...
// 获取 small bin 中倒数第二个 chunk 。
bck = tc_victim->bk;
...
bin->bk = bck;
bck->fd = bin;
//将其放入到Tcache中
tcache_put (tc_victim, tc_idx);
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
        malloc_printerr ("malloc(): smallbin double linked list corrupted");
//只对第一个chunk进行了完整性检查,后面进入循环,无检查
...
// 获取 small bin 中倒数第二个 chunk 。
bck = tc_victim->bk;
...
bin->bk = bck;
bck->fd = bin;
//将其放入到Tcache中
tcache_put (tc_victim, tc_idx);
struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};
struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};
/* The tag name of this struct is _IO_FILE to preserve historic
   C++ mangled names for functions taking FILE* arguments.
   That name should not be used in new code.  */
struct _IO_FILE
{
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
 
  /* The following pointers correspond to the C++ streambuf protocol. */
  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;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */
 
  /* 1+column number of pbase(); 0 is unknown. */
  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;
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
  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)];
};
/* The tag name of this struct is _IO_FILE to preserve historic
   C++ mangled names for functions taking FILE* arguments.
   That name should not be used in new code.  */
struct _IO_FILE
{
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
 
  /* The following pointers correspond to the C++ streambuf protocol. */
  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;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */
 
  /* 1+column number of pbase(); 0 is unknown. */
  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;
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
  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)];
};
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 0
    get_column;
    set_column;
#endif
};
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 0
    get_column;
    set_column;
#endif
};
_IO_flush_all_lockp (do_lock=do_lock@entry=0x0)
_IO_cleanup ()
__run_exit_handlers (status=0x0, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=0x1)
__GI_exit (status=<optimized out>)
_IO_flush_all_lockp (do_lock=do_lock@entry=0x0)
_IO_cleanup ()
__run_exit_handlers (status=0x0, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=0x1)
__GI_exit (status=<optimized out>)
int _IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_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;
  
    fp = fp->_chain; //遍历链表
    }
    [...]
}
int _IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_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;
  
    fp = fp->_chain; //遍历链表
    }
    [...]
}
      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)
      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->_mode = 0
fp->_IO_write_ptr > fp->_IO_write_base
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_vtable_offset (fp) == 0
fp->_mode > 0
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
       const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
           __progname, __progname[0] ? ": " : "",
           file, line,
           function ? function : "", function ? ": " : "",
           assertion);
fflush (stderr);
abort ();
}
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
       const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
           __progname, __progname[0] ? ": " : "",
           file, line,
           function ? function : "", function ? ": " : "",
           assertion);
fflush (stderr);
abort ();
}
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 ((old_top == initial_top (av) && old_size == 0) ||
        ((unsigned long) (old_size) >= MINSIZE &&
         prev_inuse (old_top) &&
         ((unsigned long) old_end & (pagesize - 1)) == 0));
._chain => chunk_addr1
chunk_addr1
{
  file = {
    _flags = "/bin/sh\x00", //对应此结构体首地址(fp)
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x1,
      ...
      _mode = 0x0, //一般不用特意设置
      _unused2 = '\000' <repeats 19 times>
  },
  vtable = chunk_addr2
}
chunk_addr2
{
  __dummy = 0x0,
  __dummy2 = 0x0,
  __finish = 0x0,
  __overflow = system_addr,
    ...
}
._chain => chunk_addr1
chunk_addr1
{
  file = {
    _flags = "/bin/sh\x00", //对应此结构体首地址(fp)
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x1,
      ...
      _mode = 0x0, //一般不用特意设置
      _unused2 = '\000' <repeats 19 times>
  },

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 6
支持
分享
最新回复 (2)
雪    币: 3004
活跃值: (30866)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-9-3 23:09
1
雪    币: 2038
活跃值: (3847)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-9-4 08:41
0
游客
登录 | 注册 方可回帖
返回
//