Skip to content

内存分配与映射

在计算机科学与逆向工程领域,理解二进制文件的静态结构(在磁盘上)与动态布局(在内存中)之间的映射关系至关重要。IDA Pro 等反汇编工具中显示的 .text, .plt, .got, .tls 等“节”(Sections)与用户提供的内存布局图中的“段”(Segments)存在着严密的对应与转换关系。

以下是对这一体系的全面、专业讲解,涵盖 ELF 文件格式、链接视图与执行视图的差异,以及各类关键节的具体含义。

1. 核心概念:链接视图与执行视图

要理解 IDA 中的区段与内存图的关系,首先需要区分两种看待二进制文件的视角:

  • 链接视图 (Linking View):

    • 基本单位:节 (Section)。

    • 描述对象:编译器和链接器。

    • 作用:将代码和数据按照逻辑功能分类(如代码归一类,只读数据归一类,可读写数据归一类)。IDA 默认主要展示的就是这种视图。

  • 执行视图 (Execution View):

    • 基本单位:段 (Segment)。

    • 描述对象:操作系统加载器 (Loader)。

    • 作用:告诉操作系统如何将文件映射到内存中。操作系统并不关心具体的函数在哪里,只关心哪些内存区域是“可读可执行”的(RX),哪些是“可读可写”的(RW)。

    • 联系:一个“段”通常包含多个属性相似的“节”。例如,.text(代码)和 .rodata(只读数据)通常被合并进同一个可执行段(Text Segment)。

2. 标准代码与数据节 (Standard Code & Data Sections)

这是程序运行最基础的部分,对应常规的逻辑区域:

代码与指令类

  • .text:

    • 存放程序的核心机器代码(Machine Code)。

    • 归属:Text Segment。

    • 权限:Read-Execute (RX)。禁止写入,以防止代码被篡改。

  • .init / .fini:

    • 存放程序初始化和终止时的代码。.init 在 main 函数之前执行(用于环境准备),.fini 在 main 结束后执行。

    • 归属:通常合并入 Text Segment。

数据类

  • .rodata (Read-Only Data):

    • 存放常量数据,如字符串字面量("Hello World")、const 修饰的全局变量、switch 语句的跳转表。

    • 归属:Text Segment 或独立的只读段 (RO)。

  • .data:

    • 存放已初始化的全局变量和静态变量(例如 int count = 10;)。

    • 归属:Data Segment。

    • 权限:Read-Write (RW)。

  • .bss (Block Started by Symbol):

    • 存放未初始化或初始化为 0 的全局变量和静态变量。

    • 特点:在磁盘文件中不占用实际空间(只有大小记录),但在内存加载时会分配空间并自动清零。

    • 归属:紧随 Data Segment 之后。

3. 线程局部存储节 (Thread Local Storage Sections)

在多线程程序中,某些变量需要对每个线程“私有”,即每个线程都有一份独立的副本。

  • .tls / .tdata (Thread Local Data):

    • 存放已初始化的线程局部变量(例如使用 __thread int i = 5; 定义的变量)。

    • 机制:ELF 文件中存储的是这些变量的“初始镜像”。每当系统创建一个新线程时,都会分配一块新的内存区域,并将 .tdata 的内容复制进去。

    • 寻址:在 x86_64 Linux 中,通常通过 FS 段寄存器进行访问(例如 fs:0x28)。

  • .tbss (Thread Local BSS):

    • 存放未初始化的线程局部变量。

    • 机制:同样不占用磁盘空间,线程创建时分配并清零。

4. 初始化与终止数组节 (Initialization & Termination Arrays)

除了 .init 和 .fini 代码段外,现代二进制文件(尤其是 C++ 程序)依赖函数指针数组来管理构造和析构。

  • .init_array:

    • 存放函数指针数组。这些函数会在 main 函数之前被依次调用。

    • 应用:C++ 全局对象的构造函数、__attribute__((constructor)) 标记的 C 函数。

    • 逆向重点:这是恶意代码或加壳程序隐藏初始化逻辑的常见位置。

  • .fini_array:

    • 存放函数指针数组。这些函数会在 main 返回后或 exit() 被调用时执行。

    • 应用:C++ 全局对象的析构函数、__attribute__((destructor)) 标记的函数。

  • .preinit_array:

    • 在 .init_array 之前执行,通常用于极其底层的运行时库初始化,用户代码很少涉及。
  • .ctors / .dtors:

    • 旧版 GCC 编译器生成的构造/析构列表,功能分别等同于 .init_array 和 .fini_array,在现代 ELF 中已较少见。

5. 动态链接类节 (Dynamic Linking Sections)

用于处理对外部共享库(Shared Libraries)的依赖和调用。

  • .interp:

    • 存放动态链接器的路径字符串(例如 /lib64/ld-linux-x86-64.so.2)。加载器首先读取此节,加载动态链接器,然后由动态链接器接管后续加载工作。
  • .dynamic:

    • 动态链接信息的“头部”。包含了一系列标签(Tags),指向字符串表、符号表、重定位表等其他动态节的位置。
  • .plt (Procedure Linkage Table,过程链接表):

    • 本质:一小段跳板代码(Trampoline)。

    • 作用:代理外部函数调用。属于 Text Segment (RX)。

  • .got (Global Offset Table,全局偏移表):

    • 本质:指针数组。

    • 作用:存放外部数据的地址。属于 Data Segment (RW)。

  • .got.plt:

    • 专门用于存放外部函数的解析地址。延迟绑定(Lazy Binding)机制的核心数据区。

6. 符号、字符串与重定位节 (Symbol, String & Relocation)

这些节主要用于链接过程或调试,部分在运行时必须存在,部分可被剥离(Strip)。

  • .symtab / .strtab:

    • 全符号表与字符串表。包含程序中所有符号(包括静态函数、局部变量名)。

    • 特点:非运行时必需,通常被 strip 命令删除以减小体积并阻碍逆向。

  • .dynsym / .dynstr:

    • 动态符号表与字符串表。仅包含动态链接所需的符号(如导出的全局函数、引用的库函数)。

    • 特点:不可剥离,否则程序无法运行。IDA 分析时若无 .symtab,则主要依赖此表恢复函数名。

  • .rel.dyn / .rela.dyn:

    • 数据引用的重定位表。用于修正全局变量的地址。
  • .rel.plt / .rela.plt:

    • 函数引用的重定位表。用于修正 .got.plt 中的函数地址。
  • .shstrtab:

    • 节区名称字符串表。存放 .text, .data 等节名称本身的字符串。

7. IDA 中的 LOAD 段

在 IDA 的段列表中,经常能看到名为 LOAD 的条目。

  • 含义:LOAD 对应 ELF 程序头表(Program Header)中的 PT_LOAD 类型的段。

  • 作用:这代表了实际会被加载器映射到内存中的物理块。

  • 结构:一个 LOAD 段内部通常“包裹”了多个节。

    • 第一个 LOAD 段通常包含:ELF Header, .init, .text, .plt, .rodata(权限 R-X)。

    • 第二个 LOAD 段通常包含:.data, .bss, .got, .init_array, .tls(权限 RW-)。

8. 节与内存布局图的完整映射

结合用户提供的内存布局图(image_e7fadc.png),这种联系如下:

  1. Text Segment (低地址):

    • 对应图示:.text Section / .rodata Section。

    • 包含的 IDA 节:.text, .init, .plt, .rodata。

    • 物理特性:由第一个 LOAD 段映射而来,通常受硬件保护,不可写。

  2. Data Segment (接续):

    • 对应图示:Data Section。

    • 包含的 IDA 节:.data, .got, .got.plt, .init_array, .fini_array, .tdata (初始镜像)。

    • 物理特性:由第二个 LOAD 段映射而来,可读可写,包含已初始化的全局数据。

  3. BSS Section (接续):

    • 对应图示:BSS Section。

    • 包含的 IDA 节:.bss, .tbss (模板)。

    • 物理特性:紧跟在 Data Segment 之后,由操作系统在加载时动态分配并清零。

  4. Heap (堆):

    • 对应图示:Heap。

    • 来源:不在 ELF 文件中以“节”的形式存在。通常由 brk 或 sbrk 系统调用管理。

  5. Memory Mapping Region (mmap):

    • 对应图示:中间的 mmap 区域。

    • 来源:用于加载动态链接库(Shared Libraries)。每个被加载的 .so 文件在内存中也会拥有自己独立的 .text, .data, .bss 和 .tls 副本。

    • TLS 运行时位置:虽然 ELF 中有 .tls 节,但实际运行时的线程局部存储(TLS)区域通常由线程库(如 pthreads)在 mmap 区域或栈附近动态分配,并初始化为 ELF 中 .tdata 的内容。

  6. Stack (栈):

    • 对应图示:Stack。

    • 来源:由操作系统内核在创建进程时分配,位于高地址,向低地址增长。

总结:IDA 显示的区段列表是编译器视角的逻辑划分(功能分类);而用户提供的内存布局图是操作系统视角的物理划分(权限分类)。操作系统通过 LOAD 指令将多个节合并映射到内存中的同一个段。