Throughout the evolution of software vulnerabilities, we’ve seen one area of memory constantly exploited. Heap exploitation, as we discussed in the last blog, is one of the most targeted areas in modern systems when it comes to developing attacks. One common term that is often seen in vulnerabilities relating to the heap is “use after free”. The use-after-free vulnerability exploits a mistake made by the original author of a software and can result in devastating effects that range from remote code execution to the leaking of sensitive data.
In this blog I’ll explain what a use-after-free vulnerability is and how it works, and in the next part of this series I’ll run through an example Capture the Flag (CTF) exercise that simulates what a use-after-free in the wild looks like.
Use-after-frees are very powerful vulnerabilities that can lead to devastating consequences, and if you’ve been keeping up with iOS security vulnerabilities then you might have heard that two examples were found recently with the vulnerabilities CVE-2020-9768 and CVE-2020-9783. These are both use-after-free vulnerabilities within iOS that result in code execution; the former is in the kernel, the latter in Safari’s WebKit implementation.
These are just two recent examples of use-after-frees in the wild, but there are an endless number of ways to exploit the vulnerability that range from leaking memory, overwriting critical information, remote code execution and bypassing Address Space Layout Randomisation (ASLR). Before we dive into use-after-frees, let’s go over the heap again.
The heap is a global memory resource containing all the dynamic memory within a system. It varies greatly from the stack, but for more information on this topic I recommend you read the previous blog as it will help with understanding use-after-frees. In C, the malloc() function will allocate memory on the heap and return a pointer to the address of the allocated memory. There are other variations of malloc() such as calloc(), realloc(), and so on, but for the purpose of this blog we will focus on the malloc() function. Whenever malloc() is used, you will most likely hear of the free() function being used, which as the name indicates will free or deallocate the address of the memory allocation presented by the pointer returned from malloc().
You may be wondering how the machine tracks these allocations and frees; the answer is through a dynamic data structure known as a “linked list”. Without going into too much detail about linked lists, they are basically lists in which each block includes a pointers to the next block on the list. The linked list keeps track of the free blocks of memory within the system. For more information about linked lists I recommend reading this explanation.
Figure 1: Linked list of free blocks
The figure above demonstrates what a linked list of free blocks of memory look like. In more advanced cases it would be seen as a doubly linked list (i.e. each block contains a link to the previous and the next block), but for now a linked list will demonstrate what we’re looking for.
Now that we’ve covered the basics of allocating and freeing memory on the heap, we can discuss the vulnerability at hand. When a block of memory is freed the memory stored at that space remains there until modified or overwritten. As we mentioned earlier, the machine uses a linked list to keep track of free blocks of memory, so let’s imagine the following scenario.
We’ve allocated some memory using malloc(), which returns a pointer to an address that we will use to store a welcome message. We’ll call this pointer ptr. We’ve then used our data stored at the address that ptr points to, and when we are done with it, being the efficient and optimised programmers we are, we freed ptr.
This means that the memory address ptr was pointing to has now returned to the linked list of free blocks in Figure 1. However, despite being efficient programmers, we were careless and forgot to see if ptr is used again AFTER it’s freed.
Figure 2: The scenario so far
Now, here’s where things get interesting. Let’s assume ptr was used after we’ve freed it; this means in our current state we have a pointer being used after it was freed, hence Use After Free. Now, why is this dangerous?
Well, we’ve decided to malloc() something else which we’ll call ptr2, and this pointer points to important information that we do not want our user to see. Our machine will look at our list of free blocks and realise that the block of memory that ptr pointed to is free, thus it will reallocate that block and store the new critical information in there and return the address of the block to ptr2. At this stage both ptr and ptr2 store the address of the same block of memory, but our block of memory now contains critical information, so as an attacker we can try and get ptr to print out information by trying to activate the conditions it took to print its contents initially.
The reason this works is because the free() function operates on the memory location referenced by the pointer, and not the actual pointer itself. This means the pointer will still reference the same block of memory after it was freed, but the memory block’s address will be returned to the linked list of free blocks tracked by the computer. We call these types of pointers “dangling pointers”, which is when a pointer points to either a de-allocated block of memory or a deleted object.
Figure 3: Diagram of the use-after-free
That was a quick rundown of how the use-after-free vulnerability works; I thought it was necessary to include this since it’s not the easiest concept to grasp by looking at code. Here we’ve seen we can print the contents with a freed pointer, but what if we could alter the information?
We’ll answer that next time, when we detail exploiting a use-after-free vulnerability in a CTF.
For our latest research, and for links and comments on other research, follow our Cyber Lab on Twitter.
For our latest research, and for links and comments on other research, follow our Lab on Twitter.