RELRO

RELRO(RELocation Read Only)

先从名字上看,翻译一下:重定位只读

只读很好理解,重定位又是什么呢

重定位(RELocation)

在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是 存储函数指针的区域 。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处。

GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation。大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为 只读 .

设置符号重定向表格为只读或在程序启动时就解析并 绑定所有动态符号 ,从而减少对GOT(Global Offset Table)攻击。RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。

那么GOT表又是干什么的呢?

GOT表,PLT表

这涉及到 动态链接 的知识了: 

动态链接,是 提高程序空间效率的重要方法 。通过动态链接, 我们可以调用外部共享库中的函数,而不需要将其编译在 可执行文件 中。在运行时动态链接的过程中,PLT表和GOT表起到了至关重要的作用。

  • GOT: Global Offset Table, 全局偏移表,包含所有需要动态链接的外部函数的地址(在第一次执行后)
  • PLT: Procedure Link Table, 过程链接表 ,包含调用外部函数的 跳转指令 (跳转到GOT表中),以及 初始化外部调用指令 (用于链接器动态绑定dl_runtime_resolve)

.plt 段的工作流程:

  • 程序启动: 当程序启动时,.plt 段中的所有函数调用都指向一个跳转指令。
  • 第一次调用: 当程序 第一次 调用 printf 时,控制权转移到 .plt 中的相应入口点。
  • 动态链接器解析: 动态链接器 查找 printf 的实际地址 ,并将其更新到 .plt 表中。
  • 后续调用: 后续对 printf 的调用将 直接跳转 到解析后的地址, 避免再次调用动态链接器 。

总结:

.plt 段是动态链接的重要组成部分,允许程序在运行时调用动态库中的函数。通过 延迟绑定机制 ,.plt 提高了程序的灵活性和可扩展性。

.got 段的工作流程

  • 程序启动: 当程序启动时,.got 段中的地址可能是未定义的或指向某个默认值。
  • 动态链接器解析: 动态链接器在程序加载时会 解析 .got 中的地址 ,将其更新为实际的全局变量和函数地址。
  • 运行时访问: 程序在运行时通过 .got 中的地址访问 全局变量和函数 ,确保正确性。

总结:

.got 段是动态链接的重要组成部分,允许程序在运行时访问动态库中的全局变量和函数。通过动态链接器的 重定位机制,.got 提高了程序的灵活性和可扩展性。

有需要了解更多的师傅可以去这里看看:https://blog.csdn.net/Zheng__Huang/article/details/119484353

具体细节

gcc编译选项:

gcc -o test test.c						// 默认情况下,是Partial RELRO
gcc -z norelro -o test test.c			// 关闭,即No RELRO
gcc -z lazy -o test test.c				// 部分开启,即Partial RELRO
gcc -z now -o test test.c				// 全部开启,即Full 

-z norelro / -z lazy / -z now (关闭 / 部分开启 / 完全开启)

接下来直接在IDA和终端看了,也可以使用 pwndbg 中使用 vmmap 查看程序的映射,这种方法可以在文章“二进制安全-NX保护”中看到

┌──(root㉿Chenjx12)-[/home/chenj1x12/Desktop]
└─# gcc test.c -z -norelro -o test1
┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop]
└─# gcc test.c -z -lazy -o test2 
┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop]
└─# gcc test.c -z -now -o test3

┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop]
└─# ./test1 & ./test2 & ./test3 &                      
[1] 7675
[2] 7676
[3] 7677
[1]  + suspended (tty input)  ./test1
[2]  + suspended (tty input)  ./test2
[3]  + suspended (tty input)  ./test3

checksec一下:

image-20250123214342059

这是test1的,未开启RELRO保护的:

image-20250123220511396

这是test2的,开启了部分RELRO保护:

image-20250123220550514

这是test3的,开启的RELRO保护:

image-20250123214629538

接着在终端查看:

┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop]
└─# cat /proc/7675/maps 
555c43da6000-555c43da7000 r--p 00000000 08:01 926495                     /home/chenjx12/Desktop/test1
555c43da7000-555c43da8000 r-xp 00001000 08:01 926495                     /home/chenjx12/Desktop/test1
555c43da8000-555c43da9000 r--p 00002000 08:01 926495                     /home/chenjx12/Desktop/test1
555c43da9000-555c43daa000 rw-p 00002000 08:01 926495                     /home/chenjx12/Desktop/test1


┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop]
└─# cat /proc/7676/maps
559d6d66c000-559d6d66d000 r--p 00000000 08:01 926498                     /home/chenjx12/Desktop/test2
559d6d66d000-559d6d66e000 r-xp 00001000 08:01 926498                     /home/chenjx12/Desktop/test2
559d6d66e000-559d6d66f000 r--p 00002000 08:01 926498                     /home/chenjx12/Desktop/test2
559d6d66f000-559d6d670000 r--p 00002000 08:01 926498                     /home/chenjx12/Desktop/test2
559d6d670000-559d6d671000 rw-p 00003000 08:01 926498                     /home/chenjx12/Desktop/test2

┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop]
└─# cat /proc/7677/maps
556c31d1a000-556c31d1b000 r--p 00000000 08:01 926504                     /home/chenjx12/Desktop/test3
556c31d1b000-556c31d1c000 r-xp 00001000 08:01 926504                     /home/chenjx12/Desktop/test3
556c31d1c000-556c31d1d000 r--p 00002000 08:01 926504                     /home/chenjx12/Desktop/test3
556c31d1d000-556c31d1e000 r--p 00002000 08:01 926504                     /home/chenjx12/Desktop/test3
556c31d1e000-556c31d1f000 rw-p 00003000 08:01 926504                     /home/chenjx12/Desktop/test3

可以发现很明显的,test1(未开启relro的)只有四行,而test2和test3都有五行

原先(test1中)的最后一行,也就是权限为 rw- 的段,占 0x555c43daa000 - 0x555c43da9000 = 0x2000

(注意,都是十六进制的表示哦,0x2000代表着两个内存页)

因为加上relro编译选项以后,数据segemnt加载到内存分裂成了两个vma:

  • 包含.got的vma,在动态链接完成后就被保护起来了,属性设置为r--

  • 包含.data和.bss的vma,在运行时还需要读写访问的,属性设置为rw-

总而言之就是:

动态链接使程序的体积大大减小,出现了 动态链接器 、 .plt和.got段 ,为了避免 .got段被修改(也就是 重定向攻击 ),RELRO就将.got段修改为 只读 ,以 确保函数的正确调用 。