I can tell why it's failing, although I don't actually know what part of the system is responsible. While .dtors
is marked writable in the binary, it looks like it (along with .ctors
, the GOT, and a few other things) are being mapped into a separate, non-writable page in memory. On my system, .dtors
is getting put at 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
If I run the executable and check /proc/PID/maps
, I see:
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
are still writable in their own page, but the others in 0x8049000-0x804a000
aren't. I assume this is a security feature in the kernel (as you said, "there has been a movement toward readonly .dtors, plt, got lately"), but I don't know specifically what it's called (OpenBSD has something very similar called W^X; Linux has PaX, but not built into most kernels)
You can get around it with mprotect
, which lets you change the in-memory attributes of a page:
mprotect((void*)0x8049000, 4096, PROT_WRITE);
With that, my test program doesn't crash, but if I try to overwrite the end sentinel of .dtors
(0x8049f18
) with the address of another function, that function still doesn't execute; that part I can't figure out.
Hopefully somebody else knows what's responsible for making the page readonly, and why modifying .dtors
doesn't seem to do anything on my system