Toggle between light and dark mode.
Your selection will not be saved. From GDPR with ❤.

RELRO

A (not so well known) Exploit Mitigation Technique

Last Updated on February 21, 2009

Cover Image
Photo by Bart Christiaanse on Unsplash.

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.

What is RELRO.

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:

  • Compiler command line: gcc -Wl,-z,relro.
  • The ELF sections are reordered so that the ELF internal data sections (.got, .dtors, etc.) precede the program's data sections (.data and .bss).
  • Non-PLT Global Offsets Table (GOT) is read-only.
  • PLT-dependent GOT is still writeable.

Full RELRO:

  • Compiler command line: gcc -Wl,-z,relro,-z,now.
  • Supports all the features of Partial RELRO.
  • Bonus: The entire GOT is also (re)mapped as read-only.

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.

Partial RELRO Deep Dive.

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.

Full RELRO Deep Dive.

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.

RELRO Support in Linux Distributions.

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

Conclusion.

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.