The toddler’s introduction to Heap exploitation, Use After Free & Double free (Part 4)

This post is part of a series of articles related to x64 Linux Binary Exploitation techniques. Following up from my previous posts, we’ve started by exploring simple stack corruption bugs and their mitigation techniques and gradually moved to more complex topics. In this article and in the context of the Heap Memory exploitation, we are going to discuss about using memory that has been previously freed, a concept which is widely known as Use-After-Free (UAF)

Definition

Recall from my Heap Overflows post, that a chunk of memory can basically exist in two states, which is either in-use or free. An in-use chunk carries, along with the data payload, information about its size, the arena it belongs, the previous chunk and whether or no it is mmap’d. When a chunk is free, it is added to a particular list (tcache, fastbins etc.) depending on the current state of the program. During this state it carries information about its size as well as the memory addresses of other chunks, so it can be easily traced and reused by following a process which requires a new allocation.

Free chunks are carrying information about their size and the memory addresses of other chunks

When a program attempts to access a chunk which has been marked as free by the allocator, the result will be unpredictable since it usually depends on the program state before or after this event. This state may include the way that the faulty reference is going to be used but more importantly, what is the current content of the chunk. The condition that I just described is called Use After Free (UAF) and it is one of the most commonly encountered bugs with an impact varying from a simple program crash to arbitrary code execution.

Use After Free is a class of vulnerabilities that occurs when a program tries to dereference a pointer that points to a freed chunk.

Let’s take for example a pointer p which points to a chunk A that contains the address of a function f1. Let’s assume for some reason that A has been freed and is added to a list of free chunks. Now, imagine that at some point, A gets allocated again and this time contains the address of a function f2. As p still points to A, when it will be accessed again, it will trigger the execution of f2:

During the 2nd access, p still points to chunk A, which now contains the address of the function f2

First Fit

The Use-After-Free vulnerability class, leverages a behaviour of ptmalloc’s allocator according to which, malloc will return the address of the first chunk that matches a memory requirement. This behaviour is demonstrated in the following example, which is taken from the shellphish/how2heap repo:

If we compile and run the program we observe the following output:

At points 1,2 above the a variable points to 0x5558007bf010 which contains the string this is A

At point 3 var the a variable gets freed

At point 4 the c variable requests similar size with the var a, thus the first chunk that fits is the one that a var was pointing to

At point 5, if var a is accessed again, it will print this is C! as the chunk was overwritten by the var c

UAF Example 0

The following example has been taken from this Project Zero’s post:

Running this code will simply pop up a shell:

But why ? after all *run_calc has been set to 0 , so line 13 will evaluate to false and the execl should never run. Let’s load the program on gdb and set a break point after the first malloc and the assignment of *run_calc to 0:

As expected run_calc points to 0x00005555555592a0 which contains the zero value:

p_unicorn_counter will point to the same chunk with run_calc due to the same allocation requirement at Line 11, thus after Line 12 the chunk will look as below:

When run_calc is accessed again it will contain the value 0x2a thus the if will succeed and the execl will be called.

UAF Example 1

Consider the code depicted below:

In Line 4 we define fp as a pointer to a function which doesn’t get any parameters and returns void. In Line 15 we define a pointer of type fp that points to func1(Line 16). In Line 21 we call the function free for pointer1 and we repeat the same process for func2 and pointer2. The problem arises in Line 33 as we are re-using a pointer which has previously freed. If we compile and run the program, we get the following output:

While everything went as expected up to state [4], we observe right after, a call to func2 even though we never assigned func2’s address to the pointer pointer1. To understand what happened, lets load the program to gdb and set some break points after the malloc and free invocations:

After the first malloc and the assignment of func1’s address to pointer1, we have the following allocations:

As expected pointer1 points to 0x00005555555592a0 which contains the memory address 0x00005555555551c9 that corresponds to the address of func1:

Subsequently after the free invocation, the chunk that pointer1 points to, is added to the tcache list:

Finally, after the second malloc, the tcache is empty, since the memory size requirements for pointer2 are the same with pointer1, thus the allocator assigned the free chunk to pointer2:

As pointer1 still points to 0x00005555555592a0 the call (*pointer1)(); at Line 33 in our program will call the function func2.

UAF Example 2

Let’s see another example taken from https[:]//exploit-exersises.com/protostar/heap2:

The program contains a while loop (Lines 19–44) and acts according to the user’s input which is fetched at Line 22. Issuing an auth command followed by a string will trigger the brunch at Line 25. This will allocate space according to the auth struct size and will set the allocated bytes to zero (Line 26). If the string given after the “auth ” is less than 31 bytes (Line 27) the content will be copied to the memory space, pointed by the authVar->name variable.

The service command will use the strdup to duplicate a given string using the malloc function:

The strdup() function returns a pointer to a new string which is
a duplicate of the string s. Memory for the new string is
obtained with malloc(3), and can be freed with free(3).

The login command check the authVar->auth and will always print “please enter your password”, since there is no way to modify the auth variable based on the user input (or maybe there is ?). So, the exploitation scenario here is to make the program to believe that we are logged in. Finally, if the user enters reset, the program will call the free function for the authVar pointer. The mistake occurs from the fact that the authVar is accessed again at Line 38 after a potential call to free.

Before we run the program in gdb to see what is going on, let’s first do the maths:

The auth struct requires 36 bytes in total, this is 32 bytes for the name and 4 more for the auth integer. The allocator needs to add 8 more bytes to track the size of the chunk which creates a requirement of 0x24 bytes which will finally result a 0x30 bytes allocation due to the 16 bytes alignment. When the program calls the free function the chunk will be added to the tcache in order to serve the next (similar size) allocation requirement. Here is where the service command gets into frame. If we create an allocation requirement for 0x30 bytes, the freed chunk will be assigned to the char *service pointer. Since control the input, according to line 35, we can overwrite the auth integer value with an arbitrary one and pass the login check.

Let’s first try this assumption:

As expected the 123456789012345678901234567890AB value overwrite the integer authVar->auth which allowed us to login as admin. Let’s load the program to gdb to get a better idea. Let’s set a single breakpoint after the strcpy function and run the program:

After the “auth admin” input, we have the following chunks:

authVar points to 0x555555559ac0 and the allocated chunk is of 0x30 bytes size:

Typing reset will trigger the free function for the allocated chunk, thus it will be added to the tcache:

Typing service 123456789012345678901234567890AB will create a requirement for 34 bytes (including the space after service and the new line), thus the service pointer will point to the same location with the authVar:

Now lets type login and examine the chunk at 0x555555559ac0

The 0x0a42 value has overwritten the int auth, thus the if(authVal->auth) will be evaluated to true and let us login.

Double Free

Double free occurs when free is called more than one times for the same pointer. Let’s see an example from https://github.com/shellphish/how2heap

At lines 11–18, the program is filling up the tcache list.

Remember: Up to seven chunks of the same size are going to end up in the same tcache sublist.

At lines 20–23, the program allocates three more chunks of size 1x8 while at line 30 we have the first call to free for the pointer a. At line 39 we have a second call to free for the a pointer. Before Line 39 the fastbins list will look as below:

And after Line 39 (notice the addresses at the top and end of the list):

At Lines 42 to 44 we have three allocations which are going to be served from the fastbins, but since the first and the last chunk are the same, a and c will point to the same location 0x5555555593a0. The faulty allocation can be verified by simply running the program:

Notice in the last 3 lines that a and c are pointing to 0x55b275b703a0

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store