[ctf wiki pwn] stackoverflow:ret2dlresolve系列1(与RELRO相关的三种防护机制)

Posted 漫小牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[ctf wiki pwn] stackoverflow:ret2dlresolve系列1(与RELRO相关的三种防护机制)相关的知识,希望对你有一定的参考价值。

文章目录

1、RELRO的三种机制

GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术(read only relocation)。大概实现就是由linker指定binary的一块经过dynamic linker处理过之后的区域为只读。把关闭RELRO算上,共包括如下三种机制:

  1. 全部开启RELRO机制Partial-RELRO:在编译时增加 -z now (GOT 不可写)
  2. 部分开启RELRO机制Full-RELRO:在编译时增加 -z lazy(默认配置,GOT可写)
  3. 关闭RELRO机制: 在编译时增加 -z norelro

2、为什么要引入RELRO

动态链接的 ELF二进制文件使用全局偏移表 (GOT) 来动态解析位于共享库中的函数。同时,对GOT中地址的调用采用了过程链接表 (PLT)。.plt 部分包含直接指向位于 .got.plt 部分中的 GOT 的 x86 指令。GOT 通常包含指向这些函数在内存中共享库中的实际位置的指针。
GOT 在程序运行时动态填充。第一次调用共享函数时,GOT 包含一个指向 PLT 的指针,会调用动态链接器来查找相关函数的实际位置。然后将找到的位置写入 GOT。第二次调用函数时,GOT就包含了该函数的已知位置,这种机制称为“延迟绑定”。
对上述一些看似简单实际很复杂的机制(详细机制略),有如下三点解读:

  1. PLT 需要位于与 .text 部分的固定偏移量处。
  2. 由于 GOT 包含了程序不同部分直接使用的数据,因此需要在内存中分配一个已知的静态地址。
  3. 更重要的是,因为 GOT 是惰性绑定的,所以它需要是可写的。

由于 GOT 存在于内存中的预先定义好的位置,存在漏洞的程序就有可能被攻击者利用(例如某些整数溢出导致越界写入),并在某个受控的位置写入4个或8个字节的数据,并允许任意代码的执行。在这种情况下,就需要引入RELRO来避免这种情况的发生。

3、RELRO及机制比较

为了防止上述安全漏洞,我们需要确保链接器在执行开始时解析所有动态链接的函数,然后将 GOT 设为只读。这种技术称为 RELRO,可确保 GOT 不会被覆盖(或称之为GOT hijacking)。
编译程序时可以使用以下选项打开 RELRO:

gcc -g -O0 -Wl,-z,relro,-z,now -o <binary_name> <source_code>

也可以使用Partial-RELRO 进行编译,对应命令行中的“- z,relro”选项。
在Partial-RELRO 中,GOT 部分的非 PLT 部分(来自 readelf 输出的 .got)是只读的,但 .got.plt 仍然是可写的。而在Full-RELRO 中,整个 GOT(.got 和 .got.plt)都被标记为只读。

GOT全称 Global Offset Table 全局偏移表。
".got.plt" 是GOT的一部分,另一部分是 ".got"。
".got"存放全局变量引用地址。".got.plt"存放函数引用地址。

到这里,我们给出三种机制的比较

no-RELROPartial-RELROFull-RFELRO
.got .dynamic可写只读只读
.got.plt可写可写只读

虽然Partial-RELRO 和Full-RELRO 都对 ELF 内部数据节进行重排序,以防止它们在缓冲区溢出时被覆盖,但只有Full-RELRO 才能缓解上面提到的覆盖 GOT 条目以控制程序执行的技术。

4、RELRO实例分析

4.1 Partial-RELRO

示例代码如下:

#include <stdio.h>
#include <stdlib.h>


int main(int argc, int *argv[])

size_t *p = (size_t *) strtol(argv[1], NULL, 16);

p[0] = 0xDEADBEEF;

printf("RELRO: %p\\n", p);

return 0;

上面的代码尝试在用户在命令行上提供的地址中写入十六进制常量 0xdeadbeef。这是缓冲区溢出的简化形式,攻击者可以控制覆盖地址。
使用如下命令进行编译:

gcc -g -Wl,-z,relro -o test testcase.c

checksec的结果为:

下面可以通过objdump来查看printf的GOT表的入口:

gdb运行,依次输入如下命令:

gdb test
r 601018

结果为:

我们可以看到 GOT 被覆盖,使得 printf 现在指向攻击者控制的地址。调用 printf 时,将执行任意代码。这表明部分 RELRO 在防止 GOT 覆盖攻击方面并没有真正有效。

4.2 Full-RELRO

现在让我们使用完整的 RELRO 编译相同的程序,如下所示:

gcc -g -Wl,-z,relro,-z,now -o test testcase.c

checksec的结果为:

接着来看GOT表的位置:

和Partial-RELRO中的GOT表的位置相比,我们注意到printf GOT表的条目已从R_X86_64_JUMP_SLOT变到R_X86_64_GLOB_DAT的位置。
依次使用如下命令调试该程序:

gdb test
r 600fe0


当尝试覆盖 GOT 时,应用程序因 SIGSEGV 而崩溃。这是因为 GOT 被标记为只读,从而避免了漏洞利用。

以上是关于[ctf wiki pwn] stackoverflow:ret2dlresolve系列1(与RELRO相关的三种防护机制)的主要内容,如果未能解决你的问题,请参考以下文章

[ctf wiki pwn] stackoverflow:hctf2016-brop wp

[ctf wiki pwn] stackoverflow:hctf2016-brop wp

[CTF Wiki Pwn]Stackoverflow Lab002: ret2shellcode

[CTF Wiki Pwn]Stackoverflow Lab003: ret2syscall

[CTF Wiki Pwn]Stackoverflow Lab001: ret2text

[CTF Wiki Pwn]Stackoverflow: ret2reg