afafafaf
UAF tcache poisoning
Problem
Solution
Analysis
Given a binary a.out, the first thing we will do is to check the binary type and its security implementation.
basic analysis summary:
x64 least-bit ELF binary
dynamically linked, so it’ll depend on system library
not stripped, it’ll be easier for us to reverse engineer
Full RELRO, means that the GOT entry table is not writable
No Stack Canary, means no additional checks if the stack is overflown
NX enabled, means the stack is not executable
PIE enabled, means that the base address of the program is randomized for each running processes
Next, let’s try to decompile the binary, below is the relevant code snippet that ghidra has decompiled.
In an essence, this code will loop over through its execution and prompt user with an input that allows:
calls 80 bytes of malloc and saves up to chunk address in global array which is identified with (Q, W, E) respectively using the getid() function
delete (or rather free) chunk of malloc allocation
read the contents of the chunk
fill and edit the user data within that chunk allocation
The bug here lies within the 4th command –write to user data–, the program doesn’t do any checks to disallow write to a free’d chunk thus enabling a Use After Free vulnerability allowing us to do a tcache poisoning attack with manipulating the free’d chunk metadata.
The way this works is because tcache forms a linked list to keep track of free’d chunks. Below is a simplified diagram I made to illustrate and hopefully make it easier to understand:
because tcache always points to the last free’d chunk. It means if next we free chunk b, the will points to b, but how the tcache will know a chunk is also free and is available to use next?
this is where the fd metadata comes to play, upon freeing chunk b, the heap manager assume that the user data of that chunk is no longer in use and thus are able to use those overwrite those section with useful metadata to store useful data, such as the fd pointer. This way, when chunk b is being free’d, it will store wherever the current tcache is pointing to (in this case chunk a) to its fd metadata of the chunk that is being free’d (in this case chunk b). Next, the heap manager will replace the tcache pointer so it will point to the chunk that is just being free’d. The heap now roughly looks as follows:
Next, if the program asks for another malloc allocation, the heap manager will check what the tcache is pointing to, if its a NULL, then it will allocate a new chunk to be used, however if the tcache contain a pointer, then it will use an address pointed by it and reuse the it as a new chunk returned by malloc.
The tcache will then, overwrite its pointer to whatever the fd points at. If it's NULL (or not pointing to anything), then there’s no free chunks available anymore.
Disclaimer: the heap manager is very complex, and there are a lot of types of bin exist and the flow of free and malloc allocation of the heap depends on some factor one of which is the size of the malloc. In this case it will use the tcache bin, but for bigger allocation it will use another type of bin that works in a very different way. to read more about this follow this link: heap-bins
The above function reads the flag into the binary and saves it in the heap memory. We can check this by using gdb-pwndbg vis_heap_chunks
Exploitation
with this we can get an attack vector idea as follows:
Request a malloc allocation and free it
Edit the first 8 bytes –––the fd metadata––– of the free’d chunk to a pointer to flag chunk, manipulating tcache into thinking that there’s another free chunk and will allocate into that address that the fd points to next after the current chunk has been allocated.
Request an allocation that will allocate the first free’d chunk that we just corrupt
Request another allocation that because of reason explained above will allocate to the chunk that contains the flag
Read the contents of the flag chunk
Initially if we didn’t manipulate the heap should look something like this:
Thus if the program calls for another malloc it will reallocate the chunk b and ask for a new chunk address instead. However because we manipulate the fd it will look something like this:
and in this case, we will set the fd to point to the address of the flag chunk.
Flag
flag{Uaf_shouldn't_be_a_challenge}
Last updated