-
-
初识IO_FILE Exploitation
-
发表于: 2025-12-10 15:15 2771
-
参考链接:
记一次跟着ctiwiki学习IO_FILE Exploitation的过程,我会在有些地方调试看看,加深理解。大部分内容都是来自ctfwiki。
我这里是为了链接libc2.23版本的,方便后面调试。
下载有调试符号和没调试符号的libc

这里的the_end程序其实就是后面2018 HCTF the_end题目的源程序

查看libc文件的调试链接

更改链接为有调试符号的

修改前的效果

修改之后的效果

FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中。我们常定义一个指向 FILE 结构的指针来接收这个返回值。
FILE 结构定义在 libio.h 中,如下所示
进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。
在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr。因此在初始状态下,_IO_list_all 指向了一个有这些文件流构成的链表,但是需要注意的是这三个文件流位于 libc.so 的数据段。而我们使用 fopen 创建的文件流是分配在堆内存上的。
我们可以在 libc.so 中找到 stdin\stdout\stderr 等符号,这些符号是指向 FILE 结构的指针,真正结构的符号是
p _IO_list_all可以看到_IO_list_all是指向_IO_2_1_stderr_,而且地址位于libc.so上。注意结构体是_IO_FILE_plus,后面会说到

p _IO_2_1_stderr_再看_IO_2_1_stderr_的结构,可以看到_chain指向了_IO_2_1_stdout_

p _IO_2_1_stdout_再看_IO_2_1_stderr_的结构,可以看到_chain指向了_IO_2_1_stdin_

p _IO_2_1_stdin_看_IO_2_1_stdin_的结构,可以看到_chain是指向0x0,为空。

从这四个图可以看到链表指向是_IO_list_all --> IO_2_1_stderr --> IO_2_1_stdout --> IO_2_1_stdin
在初始状态下,链表中只有这三个文件流。
但是事实上_IO_FILE 结构外包裹着另一种结构_IO_FILE_plus,其中包含了一个重要的指针 vtable 指向了一系列函数指针。
在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8

从上面三张图可以看到_IO_2_1_stderr_,IO_2_1_stdout,_IO_2_1_stdin_都是这种结构,vtable指向的地址都是一样的。
也可以看到64位下偏移是0xd8

vtable 是 IO_jump_t 类型的指针,IO_jump_t 中保存了一些函数指针,在后面我们会看到在一系列标准 IO 函数中会调用这些函数指针
p *(struct _IO_jump_t *)0x7ffff7dd06e0查看vtable的结构,以 __GI_为前缀的符号通常是 glibc 内部用于绑定到实际实现函数的别名,其真正的函数名是去掉__GI_的

先下个源码wget 70aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6X3N6s2m8Q4x3X3g2Y4L8Y4g2Q4x3X3g2G2M7X3N6Q4x3V1k6Y4L8Y4g2Q4x3V1k6Y4L8r3W2T1j5#2)9J5c8X3N6D9K9h3u0U0i4K6u0V1x3W2)9J5k6e0t1K6i4K6u0W2N6r3q4J5i4K6u0W2P5s2Z5`.,方便后面看fread这些函数

tar -xvf glibc-2.23.tar.xz

fread 是标准 IO 库函数,作用是从文件流中读数据,函数原型如下
fread 函数的各个参数作用如下:
cat glibc-2.23/libio/iofread.c,fread 的代码位于/libio/iofread.c 中,函数名为_IO_fread,可以看到这个函数会接着调用_IO_sgetn函数。

_IO_sgetn函数定义在libio/genops.c这个文件内,grep -C 3 "_IO_sgetn" glibc-2.23/libio/genops.c,可以看到在_IO_sgetn 函数中会直接返回_IO_XSGETN

grep -C 3 "_IO_XSGETN" glibc-2.23/libio/libioP.h_IO_XSGETN是一个在 glibc 的 IO 系统内部用于实现多态调用的宏,它的定义位于头文件 libio/libioP.h中,AI是这样解释的:这里的 JUMP2是另一个宏,负责最终的函数跳转。它的作用是:根据传入的 FILE 结构体 (**FP****) 找到其对应的虚函数表 (vtable),然后从表中取出 ****__xsgetn**函数指针并调用它,同时将参数 FP, DATA, N传递过去

也就是说fread最后会调用vtable表中的__xsgetn函数指针,最后调用了__GI__IO_file_xsgetn。这个以 __GI_为前缀的符号通常是 glibc 内部用于绑定到实际实现函数的别名,其真正的函数定义就是 _IO_file_xsgetn

grep -A 60 "_IO_file_xsgetn" glibc-2.23/libio/fileops.c这个函数内还调用了vtable中的__underflow指针

类似地最终会调用系统接口 read 函数
fread --> _IO_sgetn --> _IO_XSGETN --> __xsgetn --> __IO_file_xsgetn --> __underflow --> _IO_new_file_underflow --> _IO_SYSREAD
fwrite 同样是标准 IO 库函数,作用是向文件流写入数据,函数原型如下
fwrite 函数的各个参数作用如下:
<font style="color:rgba(0, 0, 0, 0.87);">cat glibc-2.23/libio/iofwrite.c</font>fwrite 的代码位于/libio/iofwrite.c 中,函数名为_IO_fwrite,可以看到这个函数会接着调用_IO_sputn函数。

grep "_IO_sputn" glibc-2.23/libio/libioP.h,而_IO_sputn是一个宏,它的定义位于 glibc 源码的 libio/libioP.h 头文件中,AI解释如下:这个宏的核心作用是作为函数调用的路由,它会根据传入的 FILE结构体找到对应的虚函数表(vtable),并调用其中的 __xsputn函数指针 。

grep -C 3 "_IO_XSPUTN" glibc-2.35/libio/libioP.h


grep -C 90 "_IO_new_file_xsputn" glibc-2.23/libio/fileops.c在_IO_new_file_xsputn函数内还可以发现调用了_IO_OVERFLOW函数

grep -C 3 "_IO_OVERFLOW" glibc-2.35/libio/libioP.h会跳转到__overflow指针

根据上文得到的结果,会调用_IO_new_file_overflow函数

grep -C 30 "_IO_new_file_overflow" glibc-2.35/libio/fileops.c调用了_IO_do_write

类似地最终会调用系统接口 write 函数
fwrite --> _IO_sputn --> _IO_XSPUTN --> __xsputn --> _IO_new_file_xsputn --> _IO_OVERFLOW --> __overflow --> _IO_new_file_overflow --> _IO_do_write --> _IO_new_do_write --> new_do_write --> _IO_SYSWRITE
fopen 在标准 IO 库中用于打开文件,函数原型如下
在 fopen 内部会创建 FILE 结构并进行一些初始化操作,下面来看一下这个过程
cat glibc-2.23/libio/iofopen.c,这个函数在libio/iofopen.c文件内,函数名为_IO_new_fopen,该函数直接调用__fopen_internal

首先在 fopen 对应的函数__fopen_internal 内部会调用 malloc 函数,分配 FILE 结构的空间。因此我们可以获知 FILE 结构是存储在堆上的
之后会为创建的 FILE 初始化 vtable,并调用_IO_file_init 进一步初始化操作。
在_IO_file_init 函数的初始化操作中,会调用_IO_link_in 把新分配的 FILE 链入_IO_list_all 为起始的 FILE 链表中
grep -A 60 "_IO_new_file_init" glibc-2.23/libio/fileops.c这里用了另一个函数名表示初始化函数


grep -A 60 "_IO_link_in" glibc-2.23/libio/genops.c

之后__fopen_internal 函数会调用_IO_file_fopen 函数打开目标文件,_IO_file_fopen 会根据用户传入的打开模式进行打开操作,总之最后会调用到系统接口 open 函数,这里不再深入。
总结一下 fopen 的操作是
fopen --> _IO_new_fopen --> __fopen_internal --> _IO_file_fopen --> .....
fclose 是标准 IO 库中用于关闭已打开文件的函数,其作用与 fopen 相反。
功能:关闭一个文件流,使用 fclose 就可以把缓冲区内最后剩余的数据输出到磁盘文件中,并释放文件指针和有关的缓冲区
cat glibc-2.23/libio/iofclose.cfclose在libio/iofclose.c文件中的函数名是_IO_new_fclose

fclose 首先会调用_IO_unlink_it 将指定的 FILE 从_chain 链表中脱链
之后会调用_IO_file_close_it 函数,_IO_file_close_it 会调用系统接口 close 关闭文件
最后调用 vtable 中的_IO_FINISH,其对应的是_IO_file_finish 函数,其中会调用 free 函数释放之前分配的 FILE 结构
printf 和 puts 是常用的输出函数,在 printf 的参数是以'\n'结束的纯字符串时,printf 会被优化为 puts 函数并去除换行符。
puts 在源码中实现的函数是_IO_puts,这个函数的操作与 fwrite 的流程大致相同,函数内部同样会调用 vtable 中的_IO_sputn,结果会执行_IO_new_file_xsputn,最后会调用到系统接口 write 函数。
printf 的调用栈回溯如下,同样是通过_IO_file_xsputn 实现
前面我们介绍了 Linux 中文件流的特性(FILE),我们可以得知 Linux 中的一些常见的 IO 操作函数都需要经过 FILE 结构进行处理。尤其是_IO_FILE_plus 结构中存在 vtable,一些函数会取出 vtable 中的指针进行调用。
因此伪造 vtable 劫持程序流程的中心思想就是针对_IO_FILE_plus 的 vtable 动手脚,通过把 vtable 指向我们控制的内存,并在其中布置函数指针来实现。
因此 vtable 劫持分为两种,一种是直接改写 vtable 中的函数指针,通过任意地址写就可以实现。另一种是覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针。
这里演示了修改 vtable 中的指针,首先需要知道_IO_FILE_plus 位于哪里,对于 fopen 的情况下是位于堆内存,对于 stdin\stdout\stderr 是位于 libc.so 中。
根据 vtable 在_IO_FILE_plus 的偏移得到 vtable 的地址,在 64 位系统下偏移是 0xd8。之后需要搞清楚欲劫持的 IO 函数会调用 vtable 中的哪个函数。 printf 会调用 vtable 中的 xsputn,并且 xsputn 的是 vtable 中第八项之后就可以写入这个指针进行劫持。
并且在 xsputn 等 vtable 函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址。比如这例子调用 printf,传递给 vtable 的第一个参数就是_IO_2_1_stdout_的地址。
类似这种,第一个参数都是fp,fp实际上就是_IO_FILE_plus的地址。

利用这点可以实现给劫持的 vtable 函数传參,比如以下这个代码,memcopy(fp,"sh",3);就是把file结构前三个字节改成了sh\0,然后劫持xsputn就可以实现system(sh)了
但是在目前 libc2.23 版本下,位于 libc 数据段的 vtable 是不可以进行写入的。

不过,通过在可控的内存中伪造 vtable 的方法依然可以实现利用。
我们首先分配一款内存来存放伪造的 vtable,之后修改_IO_FILE_plus 的 vtable 指针指向这块内存。因为 vtable 中的指针我们放置的是 system 函数的地址,因此需要传递参数 "/bin/sh" 或 "sh"。
因为 vtable 中的函数调用时会把对应的_IO_FILE_plus 指针作为第一个参数传递,因此这里我们把 "sh" 写入_IO_FILE_plus 头部。之后对 fwrite 的调用就会经过我们伪造的 vtable 执行 system("sh")。
同样,如果程序中不存在 fopen 等函数创建的_IO_FILE 时,也可以选择 stdin\stdout\stderr 等位于 libc.so 中的_IO_FILE,这些流在 printf\scanf 等函数中就会被使用到。在 libc2.23 之前,这些 vtable 是可以写入并且不存在其他检测的。


可以看到main函数里开头就打印了sleep函数的地址,我们可以根据这个地址找到libc版本是libc6_2.23-0ubuntu10_amd64
此外还有个for循环可以任意地址写入总共5个字节
思路:
● 利用的是在程序调用 exit 后,会遍历 _IO_list_all ,调用 IO_2_1_stdout 下的 vtable 中 _setbuf 函数。
● 可以先修改两个字节在当前 vtable 附近伪造一个 fake_vtable ,然后使用 3 个字节修改 fake_vtable 中 _setbuf 的内容为 one_gadget。
我们先查看_IO_2_1_stdout_在libc文件中的偏移,为0x3c5620

再根据vtable跟FILE结构的偏移是0xd8,可以算出vtable的地址是:0x3c56f8。
从这里可以看到__setbuf是第12个元素,偏移是11个0x8字节,也就是0x58

查看虚表地址附近的内容,我们需要找到一个fake_vtable地址,该地址的0x58偏移是一个libc地址,我们可以把该地址改成one_gadget。也就是fake_vtable_addr + 0x58 = libc_base + onegadget

我们可以把0x7ffff7dd26b8当成fake_vtable地址,然后在0x58偏移的位置伪造onegadget的地址

one_gadget ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.so有三个gadget,到时候三个都试一下,看哪个可以


计算一下偏移

调试的时候看到已经把目标地址改成one_gadget地址了

也成功跳转到了onegadget,不知道为什么就是拿不到shell, Got EOF了

报了这个错误,AI说是onegadget的条件不满足,三个gadget都试过了也没法,看别人的wp好像也是本地打不通。。。没辙了
wget 749K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6J5j5i4N6Q4x3X3g2Y4K9i4c8Z5N6h3u0#2M7$3g2J5j5$3!0F1N6r3g2F1N6q4)9J5k6h3y4G2L8g2)9J5c8X3y4@1k6W2)9J5k6s2N6A6K9$3W2Q4x3V1k6U0N6r3k6Q4x3X3c8U0K9r3q4D9L8r3g2F1k6$3g2K6i4K6u0r3M7X3g2X3M7#2)9J5c8X3S2W2j5h3c8K6i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3M7s2N6F1i4K6u0r3L8r3W2F1N6i4S2Q4x3V1k6#2M7$3g2J5i4K6u0V1L8h3!0V1k6g2)9J5c8X3W2G2i4K6u0V1k6X3W2D9k6g2)9J5c8U0t1H3x3e0S2Q4y4h3k6Z5j5%4c8X3i4K6g2X3N6r3S2W2i4K6g2X3k6h3&6V1i4K6u0r3N6r3S2W2i4K6g2X3k6h3&6V1wget 75aK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3I4S2N6h3&6U0K9s2m8S2k6r3I4A6j5Y4u0S2M7X3W2S2L8W2)9J5k6h3&6W2N6q4)9J5c8U0x3#2x3K6f1J5x3K6M7H3z5g2)9J5c8X3I4A6j5X3x3$3i4K6u0V1k6r3u0Y4i4K6g2X3x3W2)9J5k6e0t1K6i4K6u0V1x3s2g2T1N6h3&6@1N6e0p5H3i4K6g2X3j5h3#2V1y4U0c8Q4x3X3g2V1k6h3t1`.dpkg -x libc6-dbg_2.23-0ubuntu10_amd64.deb libc6-dbg_2.23-0ubuntu10_amd64wget 8cfK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9j5i4g2F1j5$3S2H3j5h3c8D9K9h3u0J5j5i4u0A6j5h3&6Q4x3X3g2F1k6i4c8Q4x3V1j5K6y4e0x3#2x3U0x3%4x3U0W2Q4x3V1k6D9K9h3u0U0y4W2)9#2k6U0u0Q4x3X3f1J5x3#2)9J5k6o6m8#2j5Y4g2F1N6s2f1I4x3q4)9#2k6X3q4E0k6o6j5@1i4K6u0W2k6r3g2T1dpkg -x libc6_2.23-0ubuntu10_amd64.deb libc6_2.23-0ubuntu10_amd64wget 647K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6J5j5i4N6Q4x3X3g2Y4K9i4c8Z5N6h3u0#2M7$3g2J5j5$3!0F1N6r3g2F1N6q4)9J5k6h3y4G2L8g2)9J5c8X3y4@1k6W2)9J5k6s2N6A6K9$3W2Q4x3V1k6U0N6r3k6Q4x3X3c8U0K9r3q4D9L8r3g2F1k6$3g2K6i4K6u0r3M7X3g2X3M7#2)9J5c8X3S2W2j5h3c8K6i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3M7s2N6F1i4K6u0r3L8r3W2F1N6i4S2Q4x3V1k6#2M7$3g2J5i4K6u0V1L8h3!0V1k6g2)9J5c8X3W2G2i4K6u0V1k6X3W2D9k6g2)9J5c8U0t1H3x3e0S2Q4y4h3k6Z5j5%4c8X3i4K6g2X3N6r3S2W2i4K6g2X3k6h3&6V1i4K6u0r3N6r3S2W2i4K6g2X3k6h3&6V1wget 4c2K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3I4S2N6h3&6U0K9s2m8S2k6r3I4A6j5Y4u0S2M7X3W2S2L8W2)9J5k6h3&6W2N6q4)9J5c8U0x3#2x3K6f1J5x3K6M7H3z5g2)9J5c8X3I4A6j5X3x3$3i4K6u0V1k6r3u0Y4i4K6g2X3x3W2)9J5k6e0t1K6i4K6u0V1x3s2g2T1N6h3&6@1N6e0p5H3i4K6g2X3j5h3#2V1y4U0c8Q4x3X3g2V1k6h3t1`.dpkg -x libc6-dbg_2.23-0ubuntu10_amd64.deb libc6-dbg_2.23-0ubuntu10_amd64wget 5eaK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9j5i4g2F1j5$3S2H3j5h3c8D9K9h3u0J5j5i4u0A6j5h3&6Q4x3X3g2F1k6i4c8Q4x3V1j5K6y4e0x3#2x3U0x3%4x3U0W2Q4x3V1k6D9K9h3u0U0y4W2)9#2k6U0u0Q4x3X3f1J5x3#2)9J5k6o6m8#2j5Y4g2F1N6s2f1I4x3q4)9#2k6X3q4E0k6o6j5@1i4K6u0W2k6r3g2T1dpkg -x libc6_2.23-0ubuntu10_amd64.deb libc6_2.23-0ubuntu10_amd64patchelf --set-interpreter ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/ld-2.23.so ./the_endpatchelf --replace-needed libc.so.6 ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.so ./the_endpatchelf --set-interpreter ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/ld-2.23.so ./the_endpatchelf --replace-needed libc.so.6 ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.so ./the_endreadelf -x .gnu_debuglink ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.soreadelf -x .gnu_debuglink ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.soobjcopy -R .gnu_debuglink ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.socp libc6-dbg_2.23-0ubuntu10_amd64/usr/lib/debug/lib/x86_64-linux-gnu/libc-2.23.so ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc2.23objcopy --add-gnu-debuglink=./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc2.23 ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.soobjcopy -R .gnu_debuglink ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.socp libc6-dbg_2.23-0ubuntu10_amd64/usr/lib/debug/lib/x86_64-linux-gnu/libc-2.23.so ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc2.23objcopy --add-gnu-debuglink=./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc2.23 ./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.sostruct _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; 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};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 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; 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};_IO_2_1_stderr__IO_2_1_stdout__IO_2_1_stdin__IO_2_1_stderr__IO_2_1_stdout__IO_2_1_stdin_struct _IO_FILE_plus{ _IO_FILE file; IO_jump_t *vtable;}struct _IO_FILE_plus{ _IO_FILE file; IO_jump_t *vtable;}void * funcs[] = { 1 NULL, // "extra word" 2 NULL, // DUMMY 3 exit, // finish 4 NULL, // overflow 5 NULL, // underflow 6 NULL, // uflow 7 NULL, // pbackfail 8 NULL, // xsputn #printf 9 NULL, // xsgetn 10 NULL, // seekoff 11 NULL, // seekpos 12 NULL, // setbuf 13 NULL, // sync 14 NULL, // doallocate 15 NULL, // read 16 NULL, // write 17 NULL, // seek 18 pwn, // close 19 NULL, // stat 20 NULL, // showmanyc 21 NULL, // imbue};void * funcs[] = { 1 NULL, // "extra word"