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. Summarising on my previous posts, we began by exploring simple stack corruption bugs and their mitigation techniques and gradually progressed to more complex topics. In this article, I will delve into the concept of using memory that has been previously freed, a bug known as Use-After-Free (UAF) in the context of Heap Memory exploitation.

For those who just “joined”, here is what I have covered so far:
Stack Overflows:
- Basic Buffer Overflows (No ASLR, DEP/NX, Stack Canaries)
- Return into libc
- R.O.P Chains
- Stack Canaries
- Bypassing Address Space Layout Randomisation
Heap Exploitation:
Definitions
Recall from my Heap Overflows post, that a chunk of memory can exist in two states: in-use or free. When in-use (allocated), the chunk carries, along with the user’s data, metadata about its size, flags (indicating if the chunk belongs to the main arena, if it was allocated using mmap and if the previous chunk is in use) and information about the size of the previous chunk, if the later is free. This way it can be easily traced and reused following a process which requires a new allocation.

When a program attempts to access a free chunk, the result is unpredictable and depends mainly in the following two factors:
- The program state before and after this event as it defines the logic according to which the illegal reference is used.
- The current content of the chunk.
The attempt to use a chunk that has previously been freed 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 the 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:

There are various techniques that leverage this class of bugs which I am going to describe in this as well as my next posts.
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:

In the above figures (1) and (2) reflect the fact that the variable a
points to 0x5558007bf010
which contains the string this is A
. In (3) a
gets freed. The program then requests a chunk (see line 32) of size similar to the one assigned to a
. It uses c
to point to this chunk and writes this is C!
to this new allocated memory space. In (5), as pointer a
is accessed again (used after free), it will print this is C!
as the chunk‘s data was overwritten.
1st fit UAF Example (1)
Lets see a more interesting example, taken from this post:
Running this code will simply pop up a shell:

But why ? after all *run_calc
has been set to 0 , so the condition at line 13 will be 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 the zero value to *run_calc
:

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 first fit logic described before, thus after Line 12
the chunk looks as below:

When run_calc
is accessed again it contains the value 0x2a thus the if will allow the call to execl
.
1st fit UAF Example (2)
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
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
.
1st fit UAF Example (3)
Let’s see another example taken from the (now dead) https://exploit-exersises.com/protostar/heap2 website:
The program contains a while
loop (Lines 19–44) and acts according to the user’s input which gets 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 bug 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 thetcache
in order to satisfy the next (similar size) allocation requirement. Here is where theservice
command gets into frame. If we create an allocation requirement for 0x30 bytes, the freed chunk will be assigned to thechar *service
pointer. Since control the input, according to line 35, we can overwrite theauth
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 size0x8
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 allocation requirements which are going to be satisfied 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