.dtors 看起来可写,但尝试写入段错误

.dtors 看起来可写,但尝试写入段错误

这是 Ubuntu 9.04,2.6.28-11-服务器,32 位 x86


$ cat test.c
main() { int *dt = (int *)0x08049f18; *dt = 1; }
$ readelf -S ./test
...
  [18] .dtors            PROGBITS        08049f14 000f14 000008 00  WA  0   0  4
...
$ ./test
Segmentation fault
$

对于新手:gcc.dtors在 elf 可执行文件中创建一个析构函数段 ,在退出后调用main()。该表长期以来都是可写的,看起来在我的情况下应该如此(参见readelf输出)。但尝试写入表会导致段错误。

我意识到最近出现了只读 .dtors、plt 的趋势,但我不明白的是readelf和段错误之间的不匹配。

答案1

这些部分被标记为 GNU_RELRO(只读重定位),这意味着一旦动态加载器修复了(在加载时,那里没有延迟重定位)所有重定位,它就会将这些部分标记为只读。请注意,大部分内容.got.plt都在另一页上,因此没有得到处理。

您可以使用 来查看链接描述文件ld --verbose,如果您搜索 RELRO,您会发现类似以下内容的内容:

.got            : { *(.got) }
. = DATA_SEGMENT_RELRO_END (12, .);
.got.plt        : { *(.got.plt) }

这意味着 RELRO 节以 12 个字节结束.got.plt(指向动态链接器函数的指针已经解析,因此可以标记为只读)。

强化的 Gentoo 项目有一些关于 RELRO 的文档,位于http://www.gentoo.at/proj/en/hardened/hardened-toolchain.xml#RELRO

答案2

我可以说出它失败的原因,尽管我实际上不知道系统的哪一部分负责。虽然.dtors在二进制文件中被标记为可写,但看起来它(与.ctors、GOT 和其他一些东西一起)被映射到内存中一个单独的、不可写的页面中。在我的系统上,.dtors被置于0x8049f14

$ readelf -S test
  [17] .ctors            PROGBITS        08049f0c 000f0c 000008 00  WA  0   0  4
  [18] .dtors            PROGBITS        08049f14 000f14 000008 00  WA  0   0  4
  [19] .jcr              PROGBITS        08049f1c 000f1c 000004 00  WA  0   0  4
  [20] .dynamic          DYNAMIC         08049f20 000f20 0000d0 08  WA  6   0  4
  [21] .got              PROGBITS        08049ff0 000ff0 000004 04  WA  0   0  4
  [22] .got.plt          PROGBITS        08049ff4 000ff4 00001c 04  WA  0   0  4
  [23] .data             PROGBITS        0804a010 001010 000008 00  WA  0   0  4
  [24] .bss              NOBITS          0804a018 001018 000008 00  WA  0   0  4

如果我运行可执行文件并检查/proc/PID/maps,我会看到:

08048000-08049000 r-xp 00000000 08:02 163678     /tmp/test
08049000-0804a000 r--p 00000000 08:02 163678     /tmp/test
0804a000-0804b000 rw-p 00001000 08:02 163678     /tmp/test

.data/.bss在自己的页面中仍然可写,但其他页面则0x8049000-0x804a000不可写。我认为这是内核中的一个安全功能(正如您所说,“最近出现了一种向只读 .dtors、plt 发展的趋势”),但我不知道它具体叫什么(OpenBSD 有一个非常类似的东西,称为宽^X; Linux 有帕X,但没有内置到大多数内核中)

您可以使用 来绕过它mprotect,它可以让您更改页面的内存属性:

mprotect((void*)0x8049000, 4096, PROT_WRITE);

这样,我的测试程序就不会崩溃,但是如果我尝试用另一个函数的地址覆盖.dtors( 0x8049f18) 的结束标记,该函数仍然不会执行;那部分我不明白。

希望其他人知道是什么导致页面只读,以及为什么修改.dtors似乎对我的系统没有任何作用

相关内容