Basic analysis
We are working with a 64-bit binary, dynamically linked, and we are also given the loader and libc
used in the remote server. Let’s check its protections using checksec
:
Interaction
When interacting with the binary, it shows us a prompt to enter our input:
Let’s try to overflow the buffer:
Good news, we have a buffer overflow. Now, we open it up with ghidra
to have a closer look.
Disassembly
The only interesting function is the main
one:
As we can see, it initialises the buffer, and then takes 0x100 bytes (256 in decimal) from our input into the buffer, leading to the overflow.
Exploitation
Thanks to the buffer overflow, we have the power to hijack the execution flow of the program in order to return to the address that we want. However, we don’t have any win
function and shellcode can’t be injected into the stack and executed due to the NX bit. Despite that, we have the write
function loaded into the binary, which gives us the possibility to perform a ret2libc
attack.
As we said before, this binary is dynamically linked, which means that it imports the functions from its libraries during runtime, rather than incorporating them directly into the binary itself when it is compiled. This process is done through both the Global Offset Table (GOT) and Procedure Linkage Table (PLT). The GOT stores entries which contain the addresses of library functions loaded at runtime (this is done by the loader), while the PLT contains a series of stubs that redirect function calls to the appropriate shared library functions. We can easily see how this works at a low level using gdb
:
When setting a breakpoint just before the call to write
inside the main
function, we can see how it first calls the PLT address of write
, which will then jump to the GOT entry containing the libc
address for write
.
ret2libc?
Ret-to-libc is an attack based on using the functions and resources already loaded in the libc
, such as the system
function and /bin/sh
string, to exploit the program.
The goal in our case would be to load the /bin/sh
string as first parameter and the make a call to system
to spawn a shell. However, we don’t know the addresses of these two, as they are loaded at runtime and randomised due to ASLR, so we have to find a way to leak these addresses at runtime. Here is when functions such as puts
or write
come into play.
As write
is already loaded in the PLT section we saw before, we can call it and pass the GOT address of write
as parameter in order to leak it. From here, we can leak the addresses of other functions in order to find the libc
the binary is using and finally calculate the offset of system
and /bin/sh
with websites like this. The write
function takes three parameters, which means that we will have to find gadgets to control rdi
, rsi
and rdx
. Sadly, we won’t find such gadgets :(
ret2csu
When we find ourselves lacking gadgets to complete our exploit, we can use a series of functions that allow the dynamic linking of the binary. In this case, we will focus on __libc_csu_init
, which contains the following two gadgets:
As we can see, we can control r13
, r14
and r15
, which will respectively go into rdi
, rsi
and rdx
. Finally, it will call the funciton calculated in [r12+rbx*8]
. If we put 0 in rbx
and 1 in rbp
, we will pass the comparison and successfully jump to r12
. With this, we have enough to perform the attack.
Attack
Firstly, we have to find the offset at which we start overwriting the return address, using gdb
:
Now we know that we must start our payload with 72 bytes of padding until we reach the return address. The following step is to leak the address using the gadgets stated above:
Finally, calculate the offset of system
and /bin/sh
and craft the final payload to spawn the shell! (Full exploit can be found here)