CTF-PWN: 什么是_IO_FILE?

ops/2024/10/31 1:23:31/

重要概念:fopen()返回的是一个结构体的指针

_IO_FILE 结构体在什么时候被创建?

_IO_FILE 结构体的实例是在程序使用标准 I/O 函数(如 fopenfclosefreadfwrite 等)时创建和管理的。这个结构体实际上是 GNU C Library (glibc) 用于处理文件流的底层实现细节。当你在程序中打开一个文件或者创建一个流时,glibc 会在后台分配和初始化一个 _IO_FILE 结构体,并返回一个指向它的指针(即 FILE* 类型)。

下面是一些常见场景,说明 _IO_FILE 结构体是如何被创建和使用的:

  1. 使用 fopen 打开文件
    当你调用 fopen 打开一个文件时,glibc 会分配一个 _IO_FILE 结构体并进行初始化。例如:

    FILE *file = fopen("example.txt", "r");
    

    在这个例子中,fopen 函数会创建一个 _IO_FILE 结构体实例来管理 example.txt 文件的读操作,并返回一个指向该结构体的指针 file

  2. 使用 fdopen 关联文件描述符和文件流
    如果你有一个现有的文件描述符,并希望将其与一个标准 I/O 流关联,可以使用 fdopen 函数:

    int fd = open("example.txt", O_RDONLY);
    FILE *file = fdopen(fd, "r");
    

    fdopen 会创建一个新的 _IO_FILE 结构体实例,并将文件描述符 fd 关联到这个结构体上。

  3. 标准输入输出
    标准输入(stdin)、标准输出(stdout)和标准错误(stderr)也是通过 _IO_FILE 结构体来管理的。它们在程序启动时由运行时库自动初始化。

    fprintf(stdout, "Hello, World!\n");
    

_IO_FILE 结构体的创建过程

当函数如 fopen 被调用时,glibc 内部会进行以下步骤:

  1. 分配内存
    glibc 会调用内存分配函数(如 malloc)为 _IO_FILE 结构体分配内存。

  2. 初始化结构体
    分配内存后,glibc 会初始化 _IO_FILE 结构体的各个字段。例如,它会设置缓冲区指针、文件描述符、文件模式等。

  3. 返回指针
    初始化完成后,glibc 会返回一个指向这个 _IO_FILE 结构体的指针,即 FILE* 类型的指针。

_IO_FILE 在文件流操作中的生命周期

  1. 创建
    当你使用标准 I/O 函数(如 fopenfdopen)打开或创建一个文件流时,glibc 会创建一个 _IO_FILE 结构体实例。

  2. 使用
    在文件流的生命周期内,所有对该文件流的读写操作(如 freadfwritefgetsfputs 等)都会通过这个 _IO_FILE 结构体来管理缓冲区、文件描述符和流的状态。

  3. 销毁
    当你调用 fclose 关闭文件流时,glibc 会执行以下操作:

    • 刷新缓冲区中的数据(如果有需要)。
    • 释放与文件流关联的资源(如缓冲区内存)。
    • 关闭文件描述符。
    • 最后,释放 _IO_FILE 结构体的内存。

示例代码

以下是一个简单的示例代码,展示了 _IO_FILE 结构体实例的创建和使用过程:

#include <stdio.h>int main() {// 打开文件,创建一个 _IO_FILE 结构体实例FILE *file = fopen("example.txt", "w");if (file == NULL) {perror("Failed to open file");return 1;}// 使用文件流进行写操作fprintf(file, "Hello, World!\n");// 关闭文件,销毁 _IO_FILE 结构体实例fclose(file);return 0;
}

在这个示例中,当调用 fopen 时,glibc 会创建并初始化一个 _IO_FILE 结构体实例。当调用 fclose 时,glibc 会销毁这个实例并释放相关资源。

_IO_FILE 结构体

在 Linux 系统中,_IO_FILE 结构体是 GNU C Library (glibc) 中实现标准 I/O (stdio) 的核心数据结构之一。它用于描述文件流(FILE*)的内部状态和缓冲区信息。理解 _IO_FILE 结构体对于某些高级的漏洞利用技术(如利用格式字符串漏洞或缓冲区溢出漏洞)非常重要。

以下是 _IO_FILE 结构体的一般布局(具体布局可能会随着 glibc 版本的不同而变化):

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;         // 锁__off64_t _offset;         // 偏移量void *__pad1;              // 填充void *__pad2;              // 填充void *__pad3;              // 填充void *__pad4;              // 填充size_t __pad5;             // 填充int _mode;                 // 模式char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];// 未使用的填充
};

关键字段

  • _flags: 用于描述文件流的状态标志,例如是否为读模式、写模式等。
  • _IO_read_ptr, _IO_read_end, _IO_read_base: 分别指向当前读取的位置、读取的结束位置和读取缓冲区的基地址。
  • _IO_write_base, _IO_write_ptr, _IO_write_end: 分别指向当前写入的位置、写入的结束位置和写入缓冲区的基地址。
  • _IO_buf_base, _IO_buf_end: 分别指向缓冲区的基地址和结束地址。
  • _IO_save_base, _IO_backup_base, _IO_save_end: 用于保存缓冲区状态的指针。
  • _markers: 指向标记结构的链表,用于支持多种流操作。
  • _chain: 指向下一个文件流的指针,形成一个文件流链表。
  • _fileno: 文件描述符。
  • _flags2: 额外的标志位。
  • _old_offset: 用于记录偏移量。
  • _cur_column: 当前列号,主要用于格式化输出。
  • _vtable_offset: 虚表偏移,用于支持面向对象的操作。
  • _shortbuf: 一个短缓冲区。
  • _lock: 指向用于同步的锁。
  • _offset: 文件流的位置偏移量。
  • 填充字段: 用于对齐和扩展。

stdinstdoutstderr指针

这些指针是程序的标准输入、标准输出和标准错误流(stdinstdoutstderr)在内存中的地址。它们是全局变量,通常在程序启动时被初始化,以指向相应的 FILE 结构体。

解释每个指针

  1. stdout (标准输出)

    • 地址:0x602020
    • 指向的地址:0x00007fe6e8e03620
  2. stdin (标准输入)

    • 地址:0x602030
    • 指向的地址:0x00007fe6e8e028e0
  3. stderr (标准错误)

    • 地址:0x602040
    • 指向的地址:0x00007fe6e8e03540

每个地址如 0x602020 是全局变量的地址,而对应的值(如 0x00007fe6e8e03620)是这些全局变量指向的 FILE 结构体实例的地址。

内存布局和用途

  1. stdout:

    • 地址0x602020
    • 指向的地址0x00007fe6e8e03620
    • 用途:标准输出通常用于打印普通输出信息,默认连接到终端的显示设备。
  2. stdin:

    • 地址0x602030
    • 指向的地址0x00007fe6e8e028e0
    • 用途:标准输入用于读取输入数据,默认连接到终端的键盘输入。
  3. stderr:

    • 地址0x602040
    • 指向的地址0x00007fe6e8e03540
    • 用途:标准错误用于打印错误信息,默认也连接到终端的显示设备。

背后的机制

在程序启动时,C 标准库(如 glibc)会初始化这几个标准流。具体来说,它们会分配相应的 FILE 结构体,并将 stdinstdoutstderr 这些全局变量指向这些结构体。

以下是一个简化的示意图,展示了这些指针和 FILE 结构体的关系:

+----------------+           +----------------+
|  0x602020      | --------> | FILE for stdout|
|  (stdout)      |           | 0x00007fe6e8e03620 |
+----------------+           +----------------++----------------+           +----------------+
|  0x602030      | --------> | FILE for stdin |
|  (stdin)       |           | 0x00007fe6e8e028e0 |
+----------------+           +----------------++----------------+           +----------------+
|  0x602040      | --------> | FILE for stderr|
|  (stderr)      |           | 0x00007fe6e8e03540 |
+----------------+           +----------------+

示例代码验证

下面是一些示例代码,可以用来验证这些指针的地址:

#include <stdio.h>int main() {printf("Address of stdout: %p\n", (void*)&stdout);printf("Address of stdin: %p\n", (void*)&stdin);printf("Address of stderr: %p\n", (void*)&stderr);printf("Pointer value of stdout: %p\n", (void*)stdout);printf("Pointer value of stdin: %p\n", (void*)stdin);printf("Pointer value of stderr: %p\n", (void*)stderr);return 0;
}

运行这段代码,你应该会看到标准流指针的地址和它们指向的 FILE 结构体的地址,这与你提供的内存地址应该是一致的。

总结

这些指针(stdoutstdinstderr)是全局变量,指向标准 I/O 流的 FILE 结构体实例。这些实例在程序启动时由 C 标准库初始化,用于管理标准输入、输出和错误流。


http://www.ppmy.cn/ops/129745.html

相关文章

基于java SSM医药住院管理系统设计和实现

基于java SSM医药住院管理系统设计和实现 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系统 &#x1f4d…

uniapp使用easyinput文本框显示输入的字数和限制的字数

uniapp使用easyinput文本框显示输入的字数和限制的字数 先上效果图&#xff1a; 整体代码如下&#xff1a; <template><view class"nameInfoContent"><uni-easyinput class"uni-mt-5" suffixIcon"checkmarkempty" v-model&quo…

AnaTraf | IT 运维的 “得力助手”:全流量回溯分析系统与网络故障排除之道

AnaTraf 网络性能监控系统NPM | 全流量回溯分析 | 网络故障排除工具AnaTraf网络流量分析仪是一款基于全流量&#xff0c;能够实时监控网络流量和历史流量回溯分析的网络性能监控与诊断系统&#xff08;NPMD&#xff09;。通过对网络各个关键节点的监测&#xff0c;收集网络性能…

Spring Cloud微服务:构建现代应用的新基石

Spring Cloud微服务&#xff1a;构建现代应用的新基石 在当今的数字化时代&#xff0c;微服务架构已成为构建大型、复杂应用系统的主流方式。而在微服务领域&#xff0c;Spring Cloud凭借其强大的功能和灵活的架构&#xff0c;成为了一颗璀璨的明星。本文将深入探讨Spring Cl…

Rust命令行,实现自动反编译Android APK包工具

Rust-CLI实现自动反编译APK Rust提供了比较好的CLI接口,可以快速的编写命令行应用, 用于日常的工具类使用。 分享一个用Rust命令行实现自动反编译Android APK包工具&#xff0c;是之前学习Rust写的一个练手小工具&#xff0c;可以快速反编译APK&#xff0c;同时也学习下用Rust…

《网络是怎样连接的》学习总结-第二章下

目录 2. 第二章 用电信号传输TCP/IP数据——探索协议栈和网卡 2.5 IP与以太网的收发操作 2.5.1 包的基本知识 2.5.2 包收发操作概览 2.5.3 生成包含接收方IP地址的IP头部 2.5.4 生成以太网用的MAC头部 2.5.5 通过ARP查询目标路由器的MAC地址 2.5.6 以太网的基本知识 2…

【计算机网络】单播帧和广播帧在一个局域网内部的传播过程

我们引入这样的一个模型。 路由器可以连接多个网络&#xff0c;在路由器的这一端&#xff0c;我们用交换机集线器连接了很多节点。 这些节点共同组成了一个局域网。 而路由器的另外两个端口又分别连接了其他的网络。MAC地址这个概念是数据链路层才拥有的东西&#xff0c;物理…

第二十一章 Vue组件通信之prop校验及单向数据流

目录 一、什么是Prop 1.1. Prop传递数据代码示例图 1.2. 演示代码App.vue 1.3. 演示代码UserInfo.vue 二、props 校验 2.1. props校验简单写法 2.1.1. 演示代码App.vue 2.1.2. 演示代码BaseProgress.vue 2.2. props校验完整写法 2.2.1. 演示代码BaseProgress.vue 2.…