Return-to-libc AKA Ret2Libc is a form of buffer overflow attack that bypasses stack execution protection mechanisms, such as the non-executable stack (NX bit). Typically when learning about buffer overflows you place your shell code on the stack and execute it . However, most modern systems use memory protection techniques such as DEP/NX which prevents you from executing shell code on the stack. To get around this protection method attackers can use Ret2LibC.
You can watch football all day but that doesn't mean you can play the game. The same thing is true about hacking. If you want to get good at something I highly recommend getting hands on and practicing it yourself.
First we need to compile the vulnerable code. Make sure you are using a linux and install GCC so you can compile the C code. To install GCC use the following command.
#include <stdio.h>
void bug(char *arg1){
char buf[8];
memcpy(buf, arg1, strlen(arg1));
printf(buf);
}
int main(int argc, char *argv[]){ bug(argv[1]);}
Copy and paste the above code into a file called vulnerable.c. Finally, compile the C code with the following command. When compiling make sure to compile it as a 32bit application, disable PIE, and disable stack canary.
You need to disable Address space layout randomization (ASLR) as that would require another blog post to explain. ASLR is a memory-protection process for operating systems (OSes) that guards against buffer-overflow attacks by randomizing the location where system executables are loaded into memory. This blog is only concerned with bypassing NX. You can use the following command to disable ASLR.
GNU Debugger(GDB) is a debugger for C (and C++). It allows you to do things like run the program up to a certain point then stop and print out the values of certain variables at that point, or step through the program one line at a time and print out the values of each variable after executing each line.If you don't have GDB installed you can use the following command.
Once GDB is installed you need to install GDB peda with the following commands.
Now that everything is set up you should be good to go. Try running the program to make sure it is working correctly.
As stated earlier Ret2Libc is a type of buffer overflow attack used to bypass the non-executable stack (NX bit). The NX bit works by marking sections of memory(stack) as non-executable. Normally when performing a buffer overflow attack you overwrite the return address with the address of your shellcode that's located on the stack. The NX bit can prevent this type of buffer overflow attack since it makes the stack non-executable meaning your shell code wont run.
To get around this you can use Return Oriented Programming(ROP) . ROP chaining is a technique attackers use to exploit software by chaining together bits of existing code called "gadgets." Instead of injecting new malicious code, you redirect the program to execute these gadgets in a specific sequence. It's like making a new story by rearranging sentences from an existing book. You can think of Ret2Libc as a very basic ROP chain.
As mentioned above you can bypass the NX bit by using existing pieces of code found in the binary. One common way of doing this is by reusing functions found in the standard C library shared object (/lib/i386-linux-gnu/libc-*.so). On linux systems this library is automatically loaded into processes memory space. This is similar to how Ntdll.dll is loaded into windows processes.
To perform the attack you need to find a function in libc to overwrite the return address with. Remember with regular buffer overflows you would place the address of your shell code as the return address but we can’t do that this time. Libc holds a bunch of functions such as malloc, printf, strcpy, and more. As an attacker you typically only care about the system() function though. This function allows you to execute a shell command such as “wget” to download malware or “/bin/sh” to get a command prompt.
(Pic from “Red Team Notes”)
As you can see in the above image the first step is to find the address of system() and overwrite the return address with it. This eventually gets placed in the EIP register and is executed by the CPU. You must also find the address of the exit() function and put that on the stack, this is only there so the program exits cleanly. Finally, you must put the address of “/bin/sh” on the stack, this is passed to system() as an argument so the function looks like “system(‘/bin/sh’)”. You can normally find this string in libc. If you were to generate a payload for this exploit it would look something like this:
Note, if ASLR is disabled libc and other libraries, as well as various segments of a program, will load at consistent, predictable memory addresses each time they are executed. That's why we had to disable ASLR earlier. If ASLR is enabled we would need to somehow leak the address of a function in libc to make the attack work but this is beyond the scope of this post.
Now that you know some of the theory around Ret2Libc we can get more hands on with the attack. If you follow the Lab Setup section you should have everything you need to get started. One of the first things I like to do is run the program to make sure its working as expected. This can be done with the following command.
If everything is working correctly the program should print the word “testing” to the screen.
Next , let's verify ASLR is actually turned off. As mentioned earlier ASLR will randomize the memory address libc is loaded into. We can use the “ldd” command to see the memory address of the shared libraries. If ASLR is turned off this address will be the same each time we run the command.
As you can see in the above image the address libc is loaded into is the same each time I run the command.
Now lets run the program in our debugger. To do this you can use GDB with the following command.
Once you run the program in the debugger, set a breakpoint on the main() function using the “ b main” command. This will pause the program once it hits the main() function. After that you can run the program using the “r AAAA” command. This will run the program while passing “AAAA” as a command line argument.
Before we get started it is a good idea to check the binary to make sure NX is enabled and stack canaries are disabled. This can be done with the checksec command.
As mentioned earlier ret2libc attack is a type of buffer overflow. This means we must first locate and identify the buffer overflow vulnerability. To do so lets inspect the bug() function to see if we can spot the buffer overflow location. This can be done with the “disas bug” command.
It may be hard to spot without some practice reverse engineering binaries but our array is at [ebp-0xc]. Note that 0xc represents the digit 12 in hex. This means our buffer is 12 bytes long. Let's add another breakpoint at the end of the bug function using the following command in GDB.
We know it takes 12 bytes to fill up the array. It will take another 4 bytes to fill EBP and finally 4 more bytes to overwrite the return address.
Run the command above in GDB. After it runs you should get a segfault error. Use the “i r” command to view the registers. EBP should be filled with “42424242” and EIP should be equal to “43434343”, note “43” in hex is “C”.
Now that you know how many characters it takes to overflow the buffer and overwrite EIP we need to locate the system() function, exit() function, and the “/bin/sh” string.
As shown above we can use the “p system” and “p exit” command to get the address of those two functions. To find the address of “/bin/sh” the “find” command can be used as shown above. To build the exploit we should make a payload that looks like the following:
As you can see in the above image running the exploit drops us into a /bin/sh shell. If you made it this far you just successfully exploited a buffer overflow exploit using the ret2libc technique.
Buffer overflows have been around for a long time. When you first start out you typically throw your shell code on the stack to get code execution. However, most modern day systems set the stack to read/write only preventing you from executing code located on the stack. To get around this you can use return oriented programming(ROP) which reuses bits of code already in the executable. More specifically we used ret2libc which is a very basic ROP technique that uses code from the Libc library as this code is typically present in all binaries.
https://www.ired.team/offensive-security/code-injection-process-injection/binary-exploitation/return-to-libc-ret2libc
https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/ret2libc
https://www.youtube.com/watch?v=m17mV24TgwY&ab_channel=LiveOverflow