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 的运动,但我不明白的是readelf
segfault 和 segfault之间的不匹配。
我可以说出它失败的原因,尽管我实际上不知道系统的哪个部分负责。虽然.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
似乎对我的系统没有任何作用
这些部分被标记为 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 的文档。