前段时间给人讲课,碰上了高版本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之间用fd
、bk
相连,不同大小用fd_nextsize
、bk_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相关的结构。例如众所周知的stdin
、stdout
、stderr
,都是指向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
的代码片段
该函数调用的__fxprintf
、fflush
都能调用虚表中函数,有劫持的可能。
以下是进入__malloc_assert
的方法
不满足以下条件任一即可进入:
通常用的比较多的是第三种,因为页对齐条件较苛刻,通常修改topchunk的size都能不对齐。注意不能改的太大 ,否则会进入别的assert。这里修改的方式一般是构造堆重叠 或largebin attack
接下来讲函数的调用链,这里分__fxprintf
和fflush
两种方式
调用链为
__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
!!!(自豪)
这个函数中,完美达成malloc
、memcpy
、free
一条龙服务
什么?你的版本太高打不了free_hook
?
没关系,最后还有一个memset
可以写libc中的got表
如果需要打orw,rdx
也是可控的
<+53>mov rdx, QWORD PTR [rdi+0x28]
rdi
指向_flags
,rdi+0x28
指向_IO_write_ptr
,可控
详细题解可以看另一篇博客
只需要一次 任意写!
调用虚表_IO_wfile_jumps
中的_IO_wfile_seekoff
函数
最终目的是调用_IO_switch_to_wget_mode
函数
这里call了一个rax
相关的地址,而rax
由rdi
决定
再说调用条件,需要满足两个
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);
bck = bin_at (av, victim_index);
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;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
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;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
bck = victim->bk;
if
(__glibc_unlikely (bck->fd != victim))
malloc_printerr (
"malloc(): smallbin double linked list corrupted"
);
...
bck = tc_victim->bk;
...
bin->bk = bck;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
bck = victim->bk;
if
(__glibc_unlikely (bck->fd != victim))
malloc_printerr (
"malloc(): smallbin double linked list corrupted"
);
...
bck = tc_victim->bk;
...
bin->bk = bck;
bck->fd = bin;
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;
};
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
)];
};
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
)];
};
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
};
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
};
_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"
,
_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"
,
_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期)