5

Is there something that all kernel exploits have in common?

Sure, they all exploit the kernel, but I'm more interested in the underlying mechanisms or the result. At the moment my interest goes mostly in the direction of memory forensics. Since the kernel is stored in memory after boot I was wondering if an exploit necessarily needs to make changes in this memory region and could, therefore, be detected. Of course, if you have an exploit at that level you could disable every detection mechanism on the OS, but this is a different topic and memory forensics also doesn't always need to be conducted at OS level.

I'm not sure if memory forensics would work at all or for each exploit mechanism. Maybe there are other ways? Does detection depend on the specific kernel i.e. from Windows, Linux or Unix systems?

11

No, there is nothing all kernel exploits have in common.

Or at least, there is nothing unique to them and not present in standard, legal interaction with the kernel (such as using syscalls or accessing special filesystems). Typically, a kernel exploit involves making a syscall (an interface that allows userspace processes to communicate with the kernel) with arguments designed specially to cause unintended behavior, despite the syscall attempting to only allow valid arguments. Other exploits may involve concurrency issues (race conditions) between two syscalls. Let's look at a few examples:

CVE-2016-5195

This is better known as DirtyCow, and involves a race condition in the kernel's memory management, specifically Copy-on-write (CoW). Copy-on-write is a memory management technique that allows instantly duplicating a large amount of data, independent of how much data it is. When a range of pages of memory is CoWed, it is shared between two tasks. If one task modifies the page, only then is the page copied to allow it to be modified. That way two tasks can share the same physical memory while still not being able to interfere with each other.

The exploit involved a race condition with two syscalls, madvise() and ptrace(), between two tasks attempting to write to the same page. If a page is mapped in memory, and madvise(MADV_DONTNEED) is called, the kernel is told that the page will not be used in the future. Combining this with a ptrace(PTRACE_POKETEXT) call which writes directly to memory allowed modification of the original, read-only page due to the CoW bit being inappropriately removed. This could be exploited by writing to the vDSO, a small executable region of memory shared by all processes. If an unprivileged, malicious process were able to change the vDSO, then when it gets executed by innocent, privileged processes, those privileged processes could be compromised.

Both the madvise() call and the ptrace() call are benign actions. There is no reason to think that, by using them rapidly in a specific order, a page of memory in the kernel that is shared by many processes would end up becoming "owned" by a malicious process, allowing that process to change it however it wants. How could that possibly be detected?

CVE-2016-0728

This vulnerability involved exploiting a syscall, keyctl() for managing the in-kernel keyring facility. The bug involved an overflow of a reference counter, leading to the refcount wrapping around to zero. When a single keyring session is shared by multiple processes, the refcount is incremented. This is intended to allow the kernel to know when no one is using that memory anymore so it can free it.

Because of a bug in the kernel, there was as specific way to increment the reference counter without decrementing it afterwards, leading repetition of that action to increment the counter arbitrarily. Once this refcount leak caused the variable to wrap to zero, the kernel naturally thought that no one was using the object anymore, and that it would be fine to free it. However, there are still one or more tasks using that object when it gets freed. As a result, if they try to access it, they will be accessing unallocated memory. If the attacker then causes a memory allocation by creating another kernel object from userspace, and puts their own malicious data in there, the highly privileged kernel task that was previously referencing that memory area will instead access malicious code, resulting in its execution. This is termed a Use-After-Free vulnerability, or UAF.

Using the keyctl() system call and triggering memory allocations in the kernel are all normal, benign actions. How could such a thing possibly be detected without foreknowledge of the vulnerability? After all, the assumption is that a reference counter will not leak!

CVE-2017-1000112

The Linux kernel is able to "peek" at an existing packet of data without causing it to be removed from the receive queue, allowing a task to peek a bit of the data before actually taking it from the kernel. This is done with recvfrom(), using the MSG_PEEK flag.

A rather complicated bug in the kernel's implementation of UDP fragmentation offload (UFO) can allow an attacker who can set up a network interface with UFO to disable CRC checking, trigger fragmentation of a large payload, and copy a user-controlled number of bytes to another memory location. This can be used to overwrite an important struct with arbitrary data. A certain memory of this struct contains a pointer to a function, and if that member is modified to point to the address of malicious attacker-controlled code, the kernel can be made to execute it, compromising the system.

Configuring network interfaces is not a malicious activity, nor is disabling CRC checking, or using MSG_PEEK, but a specific combination of all this leads to overwriting a function pointer in the kernel, with devastating consequences.

Summary

It is true that many kernel exploits fall into just a few "classes" like UAF, integer overflow, refcount overflow, buffer overrun, etc., they all involve the system behaving in an unexpected way when told to perform a certain task. Whether or not this task is sane and should result in normal behavior, or is incorrect and should result in a returned error does not matter. The kernel is supposed to behave properly regardless of how one interacts with it, and the fact that there are bugs that allow completely legitimate interactions like telling the kernel that it's done with a page or creating new keyring objects to result in incorrect actions on the kernel's part is what can be abused by attackers. All interaction with the kernel should be defined, so even exploits attacking the kernel make use of defined but incorrectly implemented behavior.

There are, however, ways to prevent entire classes of exploits. For example, Control Flow Integrity (CFI) can be used to mitigate all forms of bugs that exploit out-of-order execution, like ROP. SMEP, SMAP, and grsecurity's UDEREF, while possible to be bypassed by leveraging additional bugs, can prevent all forms of unintentional access of userspace memory by the kernel, preventing exploits that involve modifying a function pointer to point to attacker-controlled userspace. These techniques cannot defeat all exploits as there are just too many, and they cannot defeat exploits using entirely new techniques (though thankfully those are rare).

Grsecurity is a company which aims to mitigate entire classes of bugs, and though it has shut down to the public (for only the time being, hopefully), there are still forks of it which are public. Many interesting exploit mitigations can be learned of by reading their last public patch source code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.