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)
For those who just “joined”, here is what we have covered so far:
Stack Overflows:
Heap Exploitation:
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.
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:
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
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.
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
.
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 thetcache
in order to serve 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 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