.data.rel.ro 节(Read-Only Data After Relocation)是 ELF 文件中一个关键的安全特性相关节区。它的核心作用,就是在程序加载时作为可写数据进行重定位操作,一旦完成,立即转为只读状态,从而堵上一个潜在的安全漏洞。
基本概念
.data.rel.ro 的全称是“重定位后只读数据”。顾名思义,它专门用来存放那些在编译时无法确定最终地址、需要在运行时进行重定位的初始化数据。但与普通的 .data 节不同,一旦动态链接器完成其地址修正工作,它所占用的内存区域就会被“上锁”,变为只读。
主要功能
- 存储需要重定位的初始化数据:例如,某些包含函数指针或全局变量地址的复杂数据结构。
- 提升运行时安全性:在加载和重定位完成后,立即将这部分数据设为只读,可以有效防止恶意代码篡改。
- 加固关键数据结构:尤其是保护全局偏移表(GOT)等相关数据,抵御特定类型的攻击。
与 RELRO 保护机制的关系
.data.rel.ro 节是现代 Linux 程序安全机制 RELRO(Relocation Read-Only) 的核心组成部分之一。
-
RELRO 机制是什么?
这是一种由 GCC、GNU 链接器 (ld) 和 Glibc 动态链接器 (ld.so) 共同实现的安全技术。其目标很明确:尽可能多地将完成重定位后的数据段设置为只读,以缩小攻击者可利用的可写内存区域。
-
三种 RELRO 级别
- No RELRO:完全关闭此保护。
- Partial RELRO(部分 RELRO):这是 GCC 的默认编译选项。它会使一些在程序初始化早期就完成使命的节区(如
.init_array、.fini_array、.dynamic 以及部分 .got)在初始化后被标记为只读。
- Full RELRO(完全 RELRO):在程序启动时,动态链接器会立即解析并绑定所有动态符号,这使得更多的区域(尤其是整个
.got.plt)在程序主逻辑开始前就变为只读,安全性最高,但会轻微增加启动时间。.data.rel.ro 节的处理是 Full RELRO 中的重要一环。
包含的内容
哪些数据会被放入这个特殊的节区呢?通常是以下几类:
- 需要重定位的全局变量(例如,指向其他共享库中变量的指针)。
- 与 GOT(全局偏移表)相关的某些数据项。
- 其他任何在运行时需要修正地址,但修正后绝不应该再被修改的数据。
与其他节区的关系
理解 .data.rel.ro,最好通过对比:
- 与
.data 节:两者都存放初始化数据。关键区别在于,.data 节全程可写,而 .data.rel.ro 在重定位后变为只读。
- 与
.rodata 节:两者在运行时都是只读。但 .rodata 存放的是编译时地址就完全确定的数据(如字符串常量),无需重定位;需要重定位的只读数据则交给 .data.rel.ro。
- 与
PT_GNU_RELRO 程序头:ELF 文件中有一个特殊的程序头类型 PT_GNU_RELRO,它明确告诉操作系统和动态链接器:“这个内存段范围内的数据,在重定位后请设为只读”。.data.rel.ro 节通常就位于这个段所描述的地址范围内。
安全意义
引入 .data.rel.ro 节,主要是为了应对一种常见攻击手法——通过篡改重定位数据来劫持程序流。它的安全价值体现在:
- 直接防御:防止攻击者修改已经完成重定位的关键数据指针。
- 攻击面缩减:减少了进程中可写的内存区域总量。
- 重点保护:特别是加固了 GOT,使得 GOT 覆写攻击(GOT overwrite)在开启 Full RELRO 后变得极为困难。
如何查看 .data.rel.ro 节
我们可以使用 readelf 工具来探查这个节区:
# 查看节区头信息,过滤出.data.rel.ro
readelf -S your_program | grep data.rel.ro
# 查看程序头,找到 GNU_RELRO 段,它描述了需要设为只读的地址范围
readelf -l your_program | grep GNU_RELRO
# 查看 RELRO 保护的整体状态(通过检查 GNU_RELRO 段是否存在及其属性)
readelf -l your_program
这些命令能帮助你确认程序是否启用了 RELRO 保护,以及 .data.rel.ro 节的具体布局。
工作原理
它的生命周期可以概括为四个清晰的步骤:
- 加载:程序启动时,
.data.rel.ro 节随其他数据段一起被加载到内存,此时属性为可读可写(RW)。
- 重定位:动态链接器开始工作,遍历该节中的所有重定位项,将里面的地址修正为正确的运行时地址。这个过程正是内存管理和动态链接的核心操作之一。
- 属性变更:所有重定位完成后,动态链接器调用
mprotect() 等系统调用,将 .data.rel.ro 节所在内存页的属性修改为只读(R)。
- 锁定运行:此后,程序正式进入
main 函数执行,这部分数据已被“锁定”,任何尝试写入的操作都会引发段错误(Segmentation Fault)。
实际应用与总结
在现代 Linux 生态中,出于安全考量,使用 GCC 等编译器编译的程序默认都会开启 Partial RELRO。这意味着 .data.rel.ro 节的处理已经成为二进制程序安全的标配。
对于安全性要求极高的场景(如 SUID 程序、网络服务),建议使用 -Wl,-z,relro,-z,now 链接器选项来启用 Full RELRO,它能最大化地利用 .data.rel.ro 等机制提供的保护。
总而言之,.data.rel.ro 节虽是一个技术细节,却是连接 ELF 文件格式、动态链接过程和系统安全机制的桥梁。它通过精巧的“先写后锁”设计,在几乎不牺牲性能的前提下,为程序筑起了一道重要的内存安全防线。
|