GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- 逆向技巧 之 ELF节(Section) 与 段(Segment)

 

一、节(Section) 详解表

节名称作用(通俗解释)代码示例
.text 代码仓库 - 存放程序实际执行的机器指令,CPU直接从这里取指令执行 int add(int a, int b) { return a + b; } → 编译后的机器码放这里
.data 已初始化的全局变量仓库 - 存放有初始值的全局/静态变量 int g_val = 100; → 100这个值存在.data
.bss 未初始化的全局变量仓库 - 只记录大小,不占磁盘空间,程序启动时清零 int g_arr[1000]; → 不占磁盘空间,只记录4000字节
.rodata 只读数据仓库 - 存放常量、字符串字面量,不可修改 printf("Hello World\n"); → "Hello World\n"放这里
.symtab 符号字典 - 记录所有函数名、变量名及其地址,链接时使用 int foo(); int bar = 1; → foo、bar的名字和地址记录在此
.strtab 字符串仓库 - 存放符号名称的字符串,配合.symtab使用 "main""printf""g_val" 等字符串存在这里
.shstrtab 节名仓库 - 存放所有节名称的字符串 ".text"".data"".bss" 等节名字符串
.rel.text / .rela.text 代码重定位表 - 记录代码中哪些地址需要在链接时修正 call printf → printf地址未知,记录需要修正的位置
.rel.data / .rela.data 数据重定位表 - 记录数据段中哪些地址需要链接时修正 int *p = &g_val; → g_val地址记录在重定位表
.dynsym 动态符号字典 - 运行时动态链接用的符号表(.symtab的子集) 共享库.so中导出的printf等函数信息
.dynstr 动态字符串仓库 - 动态符号对应的字符串,运行时使用 "printf""malloc" 等动态符号名
.plt 动态函数跳转桩 - 调用动态库函数的跳板,第一次调用时触发延迟绑定 call printf@plt → 先跳到PLT,PLT再找真实地址
.got 全局偏移表 - 存放全局变量的实际地址,位置无关代码通过它访问全局变量 mov rax, [rip + g_val@got] → 通过GOT找变量地址
.got.plt 函数地址表 - 配合PLT存放动态函数的实际地址,延迟绑定后更新 第一次调用printf后,这里存储printf的真实地址
.init 程序初始化代码 - main()之前执行的初始化逻辑 C++全局对象构造、__attribute__((constructor))函数
.fini 程序清理代码 - main()返回后执行的清理逻辑 C++全局对象析构、__attribute__((destructor))函数
.init_array 初始化函数指针数组 - 存放所有需要在main前调用的函数指针 __attribute__((constructor)) void init() {} → 函数指针存这里
.fini_array 清理函数指针数组 - 存放所有需要在main后调用的函数指针 __attribute__((destructor)) void cleanup() {}
.ctors C++构造函数列表 - 全局C++对象的构造函数指针(旧式) MyClass g_obj; → MyClass::MyClass()的指针存这里
.dtors C++析构函数列表 - 全局C++对象的析构函数指针(旧式) MyClass g_obj; → MyClass::~MyClass()的指针存这里
.debug_info 调试信息 - DWARF格式的变量类型、行号等调试数据 gcc -g 编译后生成,gdb靠这个知道变量类型和名称
.eh_frame 异常处理框架 - C++异常处理和栈展开的元数据 try { throw 1; } catch(...) {} → 异常处理信息
.comment 注释信息 - 编译器版本等元数据,不影响运行 GCC: (Ubuntu 11.3.0) 11.3.0
.interp 解释器路径 - 记录动态链接器路径 /lib64/ld-linux-x86-64.so.2
.dynamic 动态链接信息 - 记录依赖的共享库、GOT/PLT位置等动态链接配置 NEEDED libm.so.6 → 告诉系统需要加载哪些.so
.hash / .gnu.hash 符号哈希表 - 加速符号查找的哈希索引 动态链接时快速找到printf符号的位置
.note 附加说明信息 - ABI版本、构建ID等元数据 NT_GNU_BUILD_ID: 唯一标识这个二进制的hash值
.tdata 线程局部已初始化数据 - 每个线程独立的有初值变量 __thread int t_val = 42; → 每线程各有一份42
.tbss 线程局部未初始化数据 - 每个线程独立的无初值变量 __thread int t_count; → 每线程各有一份,初始为0

二、段(Segment/Program Header) 详解表

段类型(Type)通俗名称作用解释包含的典型节
PT_LOAD 可加载段 最重要的段!操作系统根据它把文件内容加载到内存,有多个,分别对应只读区和读写区 .text + .rodata 或 .data + .bss
PT_DYNAMIC 动态链接段 告诉动态链接器如何完成动态链接(依赖哪些库、GOT在哪里等) .dynamic
PT_INTERP 解释器段 指定动态链接器的路径,内核加载程序时先找到链接器 .interp
PT_NOTE 注释段 存放系统/工具所需的附加信息,不影响运行 .note.ABI-tag.note.gnu.build-id
PT_PHDR 段头表段 描述Program Header Table自身在文件和内存中的位置 Program Header Table本身
PT_TLS 线程局部存储段 定义线程局部变量的模板,每个线程创建时复制一份 .tdata.tbss
PT_GNU_STACK 栈属性段 标记程序栈是否可执行(现代程序栈不可执行,防止栈溢出攻击) 无实际内容,只有权限标志
PT_GNU_RELRO 只读重定位段 动态链接完成后,将这部分内存改为只读(安全加固) .got.dynamic.init_array
PT_GNU_EH_FRAME 异常处理段 指向异常处理信息的位置,C++异常和栈展开使用 .eh_frame_hdr
PT_SHLIB 保留段 历史保留类型,现代系统不使用 -

三、节(Section) → 段(Segment) 映射关系表

text
【可执行文件典型映射】

┌─────────────────────────────────────────────┐
│           ELF文件 (磁盘上)                   │
│                                             │
│  节(Section)          段(Segment)           │
│  ─────────────────────────────────────────  │
│  .interp          ──► PT_INTERP             │
│  .interp          ──► PT_LOAD[0] (只读段)   │
│  .note            ──► PT_NOTE               │
│  .text            ──► PT_LOAD[0] (只读段)   │
│  .rodata          ──► PT_LOAD[0] (只读段)   │
│  .eh_frame_hdr    ──► PT_GNU_EH_FRAME       │
│  .eh_frame        ──► PT_LOAD[0] (只读段)   │
│  .init_array      ──► PT_LOAD[1] (读写段)   │
│  .fini_array      ──► PT_LOAD[1] (读写段)   │
│  .dynamic         ──► PT_DYNAMIC            │
│  .dynamic         ──► PT_LOAD[1] (读写段)   │
│  .got             ──► PT_GNU_RELRO          │
│  .got.plt         ──► PT_LOAD[1] (读写段)   │
│  .data            ──► PT_LOAD[1] (读写段)   │
│  .bss             ──► PT_LOAD[1] (读写段)   │
│  .tdata           ──► PT_TLS                │
│  .tbss            ──► PT_TLS                │
└─────────────────────────────────────────────┘
节名称所属段(Segment)内存权限原因说明
.text PT_LOAD[0] r-x (读+执行) 代码需要执行,不能写(防篡改)
.rodata PT_LOAD[0] r-x 或 r-- 只读数据,不能修改
.interp PT_LOAD[0] + PT_INTERP r-- 解释器路径,只需读取
.eh_frame PT_LOAD[0] + PT_GNU_EH_FRAME r-- 异常处理信息,只读
.plt PT_LOAD[0] r-x PLT跳转代码,需要执行
.init / .fini PT_LOAD[0] r-x 初始化/清理代码,需要执行
.data PT_LOAD[1] rw- (读+写) 全局变量需要读写
.bss PT_LOAD[1] rw- 全局变量需要读写
.got.plt PT_LOAD[1] rw- 延迟绑定需要运行时修改
.dynamic PT_LOAD[1] + PT_DYNAMIC rw- 动态链接信息需要读写
.init_array PT_LOAD[1] + PT_GNU_RELRO rw-r-- 链接后变只读(安全)
.got PT_LOAD[1] + PT_GNU_RELRO rw-r-- 链接后变只读(安全)
.tdata PT_LOAD[1] + PT_TLS rw- 线程局部变量需要读写
.tbss PT_TLS rw- 线程局部变量需要读写
.symtab 无段映射 不加载 仅用于链接和调试,不需加载到内存
.strtab 无段映射 不加载 仅用于链接和调试
.debug_* 无段映射 不加载 仅调试器使用,不加载到内存

四、完整示意图

text
磁盘文件                          内存布局
─────────────────────────────────────────────────────────
ELF Header
Program Headers
                                 ┌──────────────────────┐
.interp    ┐                     │  只读可执行段          │
.text      │  PT_LOAD[0]  ──►   │  (r-x)               │
.plt       │  r-x                │  .interp              │
.rodata    │                     │  .text / .plt         │
.eh_frame  ┘                     │  .rodata / .eh_frame  │
                                 └──────────────────────┘
                                 ┌──────────────────────┐
.init_array ┐                    │  读写数据段            │
.fini_array │                    │  (rw-)               │
.dynamic    │  PT_LOAD[1]  ──►  │  .init_array (→只读)  │
.got        │  rw-               │  .got        (→只读)  │
.got.plt    │                    │  .dynamic             │
.data       │                    │  .got.plt             │
.bss        ┘                    │  .data / .bss         │
                                 └──────────────────────┘
Section Headers
.symtab                          (不加载到内存)
.strtab                          (不加载到内存)  
.debug_info                      (不加载到内存)

五、快速验证命令

Bash
# 查看所有节
readelf -S ./a.out

# 查看所有段
readelf -l ./a.out

# 查看节和段的映射关系(最直观!)
readelf -l ./a.out
# 输出中 "Section to Segment mapping" 部分就是映射关系

# 查看符号表
readelf -s ./a.out

# 查看动态链接信息
readelf -d ./a.out
text
# readelf -l 输出示例:
Section to Segment mapping:
  Segment Sections...
   00     .interp
   01     .interp .note .text .rodata .eh_frame   ← PT_LOAD[0]
   02     .init_array .fini_array .dynamic .got .data .bss  ← PT_LOAD[1]
   03     .dynamic                                 ← PT_DYNAMIC
   04     .note                                    ← PT_NOTE
   05     .eh_frame_hdr                            ← PT_GNU_EH_FRAME
   06                                              ← PT_GNU_STACK
   07     .init_array .fini_array .dynamic .got    ← PT_GNU_RELRO

posted on 2026-04-07 04:39  GKLBB  阅读(9)  评论(0)    收藏  举报