After a discussion I had with Sebastian Krahmer about RELRO (Relocation Read-Only), I did a little writeup on this exploit mitigation technique for my own purpose. I thought others might also find this information to be beneficial, so I published it here.
RELRO is a generic exploit mitigation technique to harden the data sections of an Executable and Linkable Format (ELF) binary or process. RELRO has two different modes, which are described in detail below.
Partial RELRO:
Full RELRO:
Both Partial and Full RELRO reorder the ELF internal data sections to protect them from being overwritten in the event of a buffer overflow in the program's data sections (.data and .bss), but only Full RELRO mitigates the popular technique of modifying a GOT entry to get control over the program execution flow.
In the following, I will demonstrate the use of the RELRO mitigation technique. I used Ubuntu 8.10 as a platform.
The following test program takes a memory address and writes the value 0x41414141 at that address.
#include <stdio.h>
int
main (int argc, char *argv[])
{
size_t *p = (size_t *)strtol (argv[1], NULL, 16);
p[0] = 0x41414141;
printf ("RELRO: %p\n", p);
return 0;
}
Compile the test program with Partial RELRO support:
$ gcc -g -o testcase testcase.c
ⓘ Note
Recent Linux distributions have Partial RELRO enabled by default (e.g. Ubuntu 8.10). There is therefore no difference between gcc testcase.c and gcc -Wl,-z,relro testcase.c on these platforms.
I wrote a small Bash script called checkrelro.sh that can be used to check if an ELF binary or a process supports the RELRO mitigation.
Check the compiled executable with checkrelro.sh:
$ ./checkrelro.sh --file testcase
testcase - partial RELRO
Gather the GOT address of printf(3):
$ readelf -r ./testcase | grep printf
0804a00c 00000407 R_386_JUMP_SLOT 00000000 printf
Modify the GOT address of printf(3) in the debugger:
$ gdb -q ./testcase
(gdb) r 0x0804a00c
Starting program: /home/tk/Desktop/testcase 0x0804a00c
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
The debugger output shows that although Partial RELRO is used, it is still possible to gain control of the execution flow of a process by modifying arbitrary GOT entries.
Compile the test program with Full RELRO:
$ gcc -g -Wl,-z,relro,-z,now -o testcase testcase.c
Test the compiled executable with checkrelro.sh:
$ ./checkrelro.sh --file testcase
testcase - full RELRO
Gather the GOT address of printf(3):
$ readelf -r ./testcase | grep printf
08049ff8 00000407 R_386_JUMP_SLOT 00000000 printf
Modify the GOT address of printf(3):
$ gdb -q ./testcase
(gdb) r 0x08049ff8
Starting program: /home/tk/Desktop/testcase 0x08049ff8
Program received signal SIGSEGV, Segmentation fault.
0x0804842b in main (argc=Cannot access memory at address 0x0
) at testcase.c:8
8 p[0] = 0x41414141;
The debugger output shows that if Full RELRO is enabled, the attempt to overwrite a GOT address leads to an error as the GOT section is mapped read-only.
The checkrelro.sh script can also enumerate all processes running on a system and check each one of them for RELRO support.
Check all running processes on Ubuntu 8.10:
$ sudo ./checkrelro.sh --proc-all
init (1) - full RELRO
gedit (15771) - partial RELRO
sshd (16181) - partial RELRO
sshd (16193) - partial RELRO
bash (16196) - no RELRO
notification-da (16626) - partial RELRO
udevd (2542) - partial RELRO
getty (4382) - partial RELRO
..
trackerd (5659) - partial RELRO
update-notifier (5662) - partial RELRO
tracker-applet (5663) - partial RELRO
bluetooth-apple (5664) - partial RELRO
python (5666) - partial RELRO
gnome-power-man (5669) - partial RELRO
gnome-terminal (5733) - partial RELRO
gnome-pty-helpe (5735) - partial RELRO
# of Processes: 74
No RELRO : 2
Partial RELRO : 71
Full RELRO : 1
Check all running processes on openSUSE 11.1:
$ sudo ./checkrelro.sh --proc-all
init (1) - partial RELRO
acpid (1599) - partial RELRO
klogd (1621) - partial RELRO
syslog-ng (1635) - partial RELRO
dbus-daemon (1645) - partial RELRO
hald (1734) - partial RELRO
console-kit-dae (1746) - partial RELRO
hald-runner (1818) - partial RELRO
..
konsole (3421) - partial RELRO
bash (3423) - partial RELRO
sshd (3436) - partial RELRO
sshd (3439) - partial RELRO
bash (3440) - partial RELRO
krunner_lock (4565) - partial RELRO
kblankscrn.kss (4567) - partial RELRO
udevd (529) - partial RELRO
# of Processes: 78
No RELRO : 0
Partial RELRO : 77
Full RELRO : 1
In case of a buffer overflow in the program’s data sections (.data and .bss), both Partial and Full RELRO protect the ELF internal data sections from being overwritten.
Full RELRO can successfully prevent the modification of GOT entries. However, this mitigation technique is not used as a default on current Linux distributions. The only argument why Full RELRO isn't widely used is that the startup of processes is slowed down as the linker has to perform all relocations at startup time.
In consequence, the good old GOT overwrite technique can still be used to achieve reliable control of the process execution flow when exploiting »write n bytes anywhere in memory« bugs like the one I recently found in FFmpeg.
There is another interesting writeup that describes a generic way to implement a similar mitigation technique for ELF objects, even if the platform doesn't support RELRO.