How Effective is ASLR on Linux Systems?

Address Space Layout Randomization (ASLR) is an exploit mitigation technique implemented in the majority of modern operating systems. In a nutshell, the idea behind ASLR is randomizing the process’ memory space in order to prevent the attacker from finding the addresses of functions or gadgets (s)he might require to successfully complete the exploit. Since an in depth explanation of ASLR is beyond the scope of this brief blog post, if you feel like reading more about ASLR, I suggest you start with these (I,II).

The Good

Linux introduced ASLR with kernel 2.6.12 back in 2005, followed by Microsoft, who did the same thing with Vista in 2007. While Linux’s ASLR is forced on every executable, Microsoft’s implementation requires the binary to be linked with ASLR support. The aforementioned randomization affects both shared libraries and executables in order to provide a fully randomized process address space. Starting 2013, one would like to think that this mechanism is mature as well as it is widely adopted, thus increasing the overall security of the operating systems (am I being too naive?).

Linux ASLR can be configured through /proc/sys/kernel/randomize_va_space. The following values are supported:

  • 0 – No randomization. Everything is static.
  • 1 – Conservative randomization. Shared libraries, stack, mmap(), VDSO and heap are randomized.
  • 2 – Full randomization. In addition to elements listed in the previous point, memory managed through brk() is also randomized.

I don’t mean to talk about the math involved in ASLR, but a quick word is hopefully not completely uncalled for. Effectiveness of ASLR is limited by the amount of entropy assigned or available. Theoretically, a 32 bit system provides less entropy to be used with ASLR than a 64 bit one. However, other constraints apply to the amount of entropy, such as those related to memory layout. For example, in order to allow the stack to continue growing from higher memory down towards the heap, the most significant bits are usually not randomized. In some scenarios, this limits the entropy of mmap() in a 32 bit system to only 16 bits. PAX patch is available to increase this amount to 24 bits.

The Bad

Leaving the math elements aside, it’s obvious that for ASLR to be effective, all segments of a process’ memory space must be randomized. The existence of a single area in memory not randomized completely defeats the purpose of ASLR. This is so because the attacker could use that single area not randomized to locate valuable gadgets in order to build a successful exploit. This has been a recurring problem with Windows implementations, since third party software (and often Windows’ software) contained some DLLs not participating in ASLR, it was easy to build exploits leveraging those libraries. Linux kernels prior to 2.6.22 had a similar problem where VDSO (linux-vdso.so) was always located at a fixed location.

On the other hand, current Linux has its own set of problems. In spite of ASLR being forced on every process, not every memory area is randomized for all executables. The code segment (or text segment; .text) of the main binary is located at random locations only if the executable has been compiled as a Position Independent Executable (PIE). A position independent executable is compiled in such a way that can be located anywhere in memory and still execute properly without modification. This is achieved through the use of PC relative addresses instead of absolute addresses. All shared objects (.so, libraries) are compiled as PIE as it’s mandatory for them to work, thus they’re always at random memory addresses when ASLR is enabled.

Based on the above paragraph, we can assume that Linux executables not compiled as PIE are not effectively protected by ASLR, even though it might be set to 2 (Full Randomization). The attacker could leverage the .text segment, and other areas located within the main executable, such as GOT/PLT to build a successful exploit against a non-PIE executable on a system with ASLR enabled. As a result, any non-PIE executable leaves the door open to return-2-plt/GOT dereferencing and ROP attacks.

The following code is used to demonstrate that main executable is not randomized despite ASLR being enabled unless it’s compiled as PIE.

#include <stdlib.h>
#include <stdio.h>

void* getEIP () {
return __builtin_return_address(0)-0x5;
};

int main(int argc, char** argv){
printf("EBP located at: %p\n",getEIP());
return 0;
}

Execution of the above code as non-PIE is displayed on the image below.

.text not randomized on Non-PIE executable

.text not randomized on Non-PIE executable

As can be seen, libraries are located at random addresses each time, meanwhile the .text section remains static. When compiled as a PIE, the following image shows how the address of the .text section is also randomized, and therefore unguessable for the attacker.

.text is random in PIE executables

.text is random in PIE executables

The Ugly

So far so good. Non-PIE executables do not benefit from ASLR protection, so? Linux binaries are surely compiled as PIE to make the most of the available exploit mitigations, or do they? According to some studies, the Linux flavours most used as web server are CentOS, Ubuntu Server and Debian (although I guess RedHat enterprise has some nice share as well). Based on that, I’ve compiled some statistics about the number of PIEs present in the above Linux distributions. The systems studied are:

  • Ubuntu Server 12.10 x86_64 + apache2 + mysql + php5 +sshd
  • Debian 6 x86_64 + web server + mysql + php5 + sshd
  • CentOS 6.3 x86_64 + apache2 + mysql + php5 + sshd

All of them installed with the standard options. The following results were compiled using Checksec, a nicely done script that checks for the presence of common security mechanisms such as ASLR, NX, Canaries, RELRO, etc. You can obtain Checksec here. These are the numbers:

Distro Num Binaries PIE Enabled Not PIE
Ubuntu 12.10 646 111 (17.18%) 535
Debian 6 592 61 (10.30%) 531
CentOS 6.3 1340 217 (16.19%) 1123

Surprisingly enough, the use of PIE is not widely embraced by the above Linux versions. However, network daemons are usually compiled as PIE, alleviating the problem and reducing the attack surface. Whether the reason not to enable PIE is performance (PIE binaries require an extra indirection) or not, the security implications greatly compensate for that. Quick and dirty math shows that between 82.82% and 89.7% of binaries are not effectively protected by ASLR in Linux systems.

In the same way, other protections such as stack canaries and RELRO were taken into account during this exercise and the results are both uneven and shocking. However, in order to obtain a truthful vision of the state of those security mechanisms, further work is required. For example, GCC would only include stack canaries in functions that match specific criteria; in other words, functions that GCC considers a likely target for buffer overflows. The result being that the lack of canaries in some binaries does not necessarily mean the executable is missing some mitigation mechanism, it may as well not be needed at all.

Well, so much for a Sunday evening. Any questions or corrections, use the comments!

Take care.

Anuncios
Tagged with: , , ,
Publicado en exploiting, hacking, Linux
5 comments on “How Effective is ASLR on Linux Systems?
  1. Donncha dice:

    That’s a nice intro to modern ASLR. The statistics of non-PIE executables is very significant. I wonder what these stats are like for browser plugins etc.

  2. Anonymous coward dice:

    Thanks you for the great introduction and overview! 🙂 I have got one question. However the number of non-PIE binaries are really shocking for me, but if the big amount of these binaries are libraries then OS forces the ASLR for them. Am I right?

  3. Adrián dice:

    I’m glad you liked it. As stated in the article, libraries must be compiled as PIE in order to work properly, so if the system’s settings has ASLR enabled, libraries will be randomized. However, the data gathered does not include any .so (library) object.

  4. . dice:

    Wow, did not realize major distros do so badly with this aspect.. Some of the benefits of using a source based OS though, is stuff like this:

    * System-wide ASLR: PaX ASLR enabled

    * Does the CPU support NX: Yes

    COMMAND PID RELRO STACK CANARY NX/PaX PIE
    init 1 Full RELRO Canary found PaX enabled PIE enabled
    udevd 1193 Full RELRO Canary found PaX enabled PIE enabled
    syslog-ng 1946 Full RELRO Canary found PaX enabled PIE enabled
    acpid 1979 Full RELRO Canary found PaX enabled PIE enabled
    dbus-daemon 2037 Full RELRO Canary found PaX enabled PIE enabled
    arpwatch 2298 Full RELRO Canary found PaX enabled PIE enabled
    ntpd 2356 Full RELRO Canary found PaX enabled PIE enabled

  5. Funky Chicken dice:

    The following code snippet is given to demonstrate that the .text section location is not randomized, even with ASLR enabled, as long as the binary is not compiled with the -fPIE option :

    #include
    #include
    void* getEIP () {
    return __builtin_return_address(0)-0x5;
    };
    int main(int argc, char** argv){
    printf(“EBP located at: %p\n”,getEIP());
    return 0;
    }

    I thought __builtin_return_address(0) was supposed to return the return address of the function getEIP(), being the next machine instruction to be executed in main function after returning from the call to getEIP(). Substracting 0x5 to this value, I should find the address of the call to getEIP() itself (callq opcode is 5-byte long) and not the EBP as stated in the printf().

    Did I miss something ?

    I slighty modified the code to print the EBP value as well :

    #include
    #include

    #define psize (int)sizeof(char *) /* Pointer size */

    unsigned char* getBP()
    {
    if(psize==8) __asm__(“movq (%rbp), %rax”);
    else
    __asm__(“movl (%ebp), %eax”);
    }

    void* getEIP () {
    return __builtin_return_address(0);
    };

    int main(int argc, char** argv){
    printf(“address of the next machine instruction to be executed when returning from getEIP() is : %p\n”,getEIP());
    printf(“EBP for main function is : %p\n”,getBP());
    return 0;
    }

    There is no randomization at all if we compile it with ASLR disabled :

    $ cat /proc/sys/kernel/randomize_va_space
    0
    xd@Ubuntu14-VirtualBox:~/$ gcc -o getEIP2 geteip2.c
    xd@Ubuntu14-VirtualBox:~/$ ldd getEIP2
    linux-vdso.so.1 => (0x00007ffff7ffd000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7c10000)
    /lib64/ld-linux-x86-64.so.2 (0x0000555555554000)
    xd@Ubuntu14-VirtualBox:~/$ ./getEIP2
    address of the next machine instruction to be executed when returning from getEIP() is : 0x400562
    EBP for main function is : 0x7fffffffde10
    xd@Ubuntu14-VirtualBox:~/$ ./getEIP2
    address of the next machine instruction to be executed when returning from getEIP() is : 0x400562
    EBP for main function is : 0x7fffffffde10
    xd@Ubuntu14-VirtualBox:~/$ ./getEIP2
    address of the next machine instruction to be executed when returning from getEIP() is : 0x400562
    EBP for main function is : 0x7fffffffde10

    After enabling ASLR, we see that the stack is randomized, even without compiling with -fPIE option but the code section is not :

    xd@Ubuntu14-VirtualBox:~/$ sudo bash -c “echo 2 > /proc/sys/kernel/randomize_va_space”
    xd@Ubuntu14-VirtualBox:~/$ gcc -o getEIP2 geteip2.c
    xd@Ubuntu14-VirtualBox:~/$ ldd getEIP2
    linux-vdso.so.1 => (0x00007ffc4a4e0000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faea4f74000)
    /lib64/ld-linux-x86-64.so.2 (0x00005640f9816000)
    xd@Ubuntu14-VirtualBox:~/$ ./getEIP2
    address of the next machine instruction to be executed when returning from getEIP() is : 0x400562
    EBP for main function is : 0x7ffd866aad90
    xd@Ubuntu14-VirtualBox:~/$ ./getEIP2
    address of the next machine instruction to be executed when returning from getEIP() is : 0x400562
    EBP for main function is : 0x7ffe1892fa10
    xd@Ubuntu14-VirtualBox:~/$ ./getEIP2
    address of the next machine instruction to be executed when returning from getEIP() is : 0x400562
    EBP for main function is : 0x7ffe3b652780

    Best regards.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Archive
A %d blogueros les gusta esto: