这是 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
似乎对我的系统没有任何作用