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

Fix*_*xee 9 memory gcc segmentation-fault

这是 Ubuntu 9.04, 2.6.28-11-server, 32bit 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
$
Run Code Online (Sandbox Code Playgroud)

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

我意识到最近出现了一种面向只读 .dtors、plt 的运动,但我不明白的是readelfsegfault 和 segfault之间的不匹配。

Mic*_*zek 5

我可以说出它失败的原因,尽管我实际上不知道系统的哪个部分负责。虽然.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
Run Code Online (Sandbox Code Playgroud)

如果我运行可执行文件并检查/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
Run Code Online (Sandbox Code Playgroud)

.data/.bss在他们自己的页面中仍然可写,但其他页面0x8049000-0x804a000不是。我认为这是内核中的一项安全功能(正如您所说,“最近出现了只读 .dtors,plt,最近得到的运动”),但我不知道它具体叫什么(OpenBSD 有一些非常相似的东西叫做W^X ; Linux 有PaX,但没有内置到大多数内核中)

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

mprotect((void*)0x8049000, 4096, PROT_WRITE);
Run Code Online (Sandbox Code Playgroud)

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

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

  • 如果带有 PaX `mprotect` 的 OP Linux 无法使可执行页面可写或使以前可写的页面可执行,除非使用 `paxctl -m` 禁用该功能。 (3认同)

nin*_*alj 5

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

您可以看到带有 的链接描述文件ld --verbose,如果您搜索 RELRO,您会发现类似于:

.got            : { *(.got) }
. = DATA_SEGMENT_RELRO_END (12, .);
.got.plt        : { *(.got.plt) }
Run Code Online (Sandbox Code Playgroud)

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

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