Taking Control of Linux Exploit Mitigations
Hey, it's Will. In my last two blog entries, I looked at aspects of two exploit mitigations (NX and ASLR) on the Linux platform. With both cases, Linux left a bit to be desired. In this post, I will explain how to add further exploit protections to Linux.
If you want to be secure in the Windows world, you should be running Microsoft EMET. If you are running Windows Vista or later, EMET mitigates nearly the entire class of memory corruption vulnerabilities by using DEP, ASLR, ROP, and other mitigations. A tool like EMET is possible because, with Windows, ASLR can be enabled for programs and libraries that weren't explicitly built to support it.
Edit: While the set of exploit mitigations provided by EMET can be applied per application in Windows, ASLR will only be applied to the libraries loaded by the application. If an application is not linked with
/DYNAMICBASE, then the load address of the application itself will not be randomized with EMET's application-specific mitigations. If EMET is used to enable system-wide ASLR, then executables with a relocation table are randomized. Executables with no relocation table, such as those linked with
/FIXED, will not be randomized, regardless of how EMET is configured.
Sometimes things can break when EMET is used though. When these situations arise, the options are to either disable specific mitigations for certain applications or wait until vendors fix their code.
With Linux, things aren't so easy. As I described in my previous blog post, an executable must be compiled with PIE to be ASLR compatible. By default, most applications on Linux are not compiled with PIE. A simple visualization of ASLR on Linux can be achieved by running the following command multiple times:
Running this command displays the memory maps for the current process, which is
cat in the above case. First let's look at the default UbuFuzz virtual machine, which is the VM provided with the CERT BFF (UbuFuzz has ASLR disabled):
Every time the above command is executed, the code is located in the same place. From an exploitability perspective, this approach is bad because an attacker can predict the location of code in memory, which enables the use of ROP or return-to-libc style attacks.
Let's now enable ASLR by commenting out the
kernel.randomize_va_space=0 line in
/etc/sysctl.conf. Ubuntu has ASLR enabled by default, but this feature is disabled in the UbuFuzz VM to simplify fuzzing. Once ASLR is re-enabled, we run the test again:
Here notice that the stack, heap, and loaded module locations are randomized, but the application itself (
cat) is not randomized. Every time it executes, the application is loaded at the same memory location.
Grsecurity and Pax
As it turns out, it's possible to enable additional exploit mitigations in Linux. Unfortunately, the mitigations are not part of the vanilla Linux kernel. Therefore, you need to get the Linux kernel sources, apply a patch, and build your own kernel. The particular patch in question is provided by grsecurity, which also includes PaX. This patch provides additional protections that help enhance the security of a system, including various memory protections provided by PaX.
Compiling and patching your own kernel may sound scary, but it's actually not too difficult. The Insanitybit blog has provided guidance for how to build a grsecurity kernel for Ubuntu. Grsecurity has since been updated to allow an automatic configuration, which makes configuration easier. Let's run the same test on the same UbuFuzz system, but with the grsecurity kernel:
The obvious change here is that there is more randomness (higher entropy) in the addresses of the stack, heap, and loaded modules. But notice that
cat is still not randomized because
cat is not built with PIE, and therefore cannot be randomized on Linux. How do we get application addresses to be randomized on Linux? As mentioned in my previous blog post, for every application we would like to have ASLR protection, we need to compile it with PIE. How practical is this, though?
Hardened Gentoo Linux
Gentoo Linux is one of the few Linux distributions where packages are compiled from source code, rather than provided in binary format like Red Hat or Ubuntu. Setting up a Gentoo Linux system requires more "wall clock" time due to compilation requirements, and it also requires more human interaction than most other Linux distributions to configure and tweak the system to work smoothly. At least the prevalence of multi-core computer systems these days makes compilation a bit less time consuming than it was in the past.
One security benefit of Gentoo is the reduced attack surface of the platform, since you only get what you explicitly ask for. The other aspect that could make a system like Gentoo a little more difficult to attack using a ROP-style attack is the diversity inherent in the platform ecosystem. Depending on which compilation flags a Gentoo administrator believes will make the system faster, the resulting compiled code may be different among Gentoo instances. With any application that may be bruteforced by an attacker, this entropy provided by different compilation flags is not sufficient protection. In our case, though, the particular feature/variant of Gentoo of interest is called Hardened Gentoo.
Hardened Gentoo is a Gentoo profile that enables grsecurity and PaX features in the Linux kernel, and configures the toolchain (compiler, linker, etc.) to use security-enhanced features such as PIE. Because the packages are built with the hardened toolchain, packages installed on a Hardened Gentoo system will have extra exploit mitigations. Let's run the same test on a Hardened Gentoo system:
Here we can see that everything is randomized, including the executable, and the entropy is higher than a vanilla Linux system. Exploiting a memory corruption vulnerability on such a system would be quite difficult.
It is also possible to run Gentoo with a vanilla Linux kernel, but configure the toolchain to enable PIE and other protections. Packages built after this change is made will be compiled with the protections. While a system configured in this way will not be as secure as a system that runs the hardened Linux kernel, this technique may be a compromise for environments where the hardened kernel cannot be used.
A Better Example
In the above examples,
cat provides a simple example that can visualize the effects of ASLR. However,
cat really isn't a high-risk application, and due to its trivial nature, we don't expect vulnerabilities to be discovered in it. How can we check the exploit mitigation features of arbitrary programs? The script checksec.sh by Tobias Klein is useful for this purpose. Let's look at the ffmpeg program, which has a large attack surface; we can expect it to contain a number of vulnerabilities. First, on Ubuntu:
Any properties that are not green are not the most secure. In this particular case, we can see that ffmpeg on Ubuntu is not compiled with PIE, and therefore will not receive the security benefit of ASLR. This binary also only uses Partial RELRO.
Let's look at ffmpeg on a Hardened Gentoo system:
In this case, all of the exploit mitigations are present.
Compared to Windows, enabling extra exploit mitigations on Linux requires a bit more work. Although the tests demonstrated in this blog entry focus on the ASLR aspect, a grsecurity-patched (and therefore PaX-enabled) Linux system provides a large number of protections that can make exploitation more difficult. At least on x86, some of these protections may have a noticeable performance impact. While a Hardened Gentoo platform may enable the most exploit protections for the most parts of the system, this approach may not be for everyone. If you are looking to enhance the security of your Linux system, it may be worth looking into at least building a grsecurity-enabled kernel for the Linux distro that you are already using.