Today, I will show you how to use Return Oriented Programming for doing a ret2libc attack.
Foreword
This is much more harder than what we encountered earlier, unlike before we won’t have any function preloaded with strings like /bin/cat flag.txt
. It won’t even contain a system
so we will use libc.so.6
to get the system
and /bin/sh
address to spawn a shell.
What is Return-to-libc or ret2libc attack?
A “return-to-libc” attack is a computer security attack usually starting with a buffer overflow in which a subroutine return address on a call stack is replaced by an address of a subroutine that is already present in the process’ executable memory, bypassing the no-execute bit feature (if present) and ridding the attacker of the need to inject their own code.
Returning to libc is a method of exploiting a buffer overflow on a system that has a non-executable stack, it is very similar to a standard buffer overflow, in that the return address is changed to point at a new location that we can control. However since no executable code is allowed on the stack we can’t just tag in shellcode. This is the reason we use the return into libc trick and utilize a function provided by the library. We still overwrite the return address with one of a function in libc, pass it the correct arguments and have that execute for us. Since these functions do not reside on the stack, we can bypass the stack protection and execute code.
Binary Analysis
Let’s try checksec bitterman
and see what protections are enabled.
Arch: amd64-64-little |
NX is enabled that means we have a non executable stack, hence we need a ret2libc attack.Let’s load up the binary into radare2 and start analysing.
0x0040077c bfc9084000 mov edi, str.Please_enter_your_text: ; .//main.c:23 ; 0x4008c9 ; "> Please enter your text: " ; const char * s |
It uses puts
that seems vulnerable? Can’t say much without checking. So, let’s use gdb-peda:-
gdb-peda$ r |
Doing that, we get Program received signal SIGSEGV, Segmentation fault., look like the stack overflowed i.e. a buffer overflow. So, let’s continue analysing the stack and core that has been dumped.
[----------------------------------registers-----------------------------------] |
Look, at RSP register is full of our pattern, in 64-bit register we cant overwrite RIP straight away so let’s see our stack RSP, the Stack Pointer points to our input, we need to fix the return address and inject our code but for that we need to know where overflow occurs. Doing similar from what we did earlier let’s get started:-
gdb-peda$ x/xg $rsp |
x/xg
: This loads the memory address of RSP register in hex format and in 64-bit format.pattern offset < value >
: This shows after how many bytes the overflow occured.
We get the overflow limit i.e. 152.
Stage 1: Leaking the address
First, as told earlier we need to get put
address from the running process. First, we need leak address then we need to get the constant distance which means the offset for it after that we will get to execute our code.
Let’s get puts
using objdump -D bitterman | grep puts
:-
400520: ff 25 2a 07 20 00 jmpq *0x20072a(%rip) # 600c50 <puts@GLIBC_2.2.5> |
As you can see we have one address at the left and one i the right. Let m clarify this:-
PLT stands for Procedure Linkage Table which is, put simply, used to call external procedures/functions whose address isn’t known in the time of linking, and is left to be resolved by the dynamic linker at run time.
GOT stands for Global Offsets Table and is similarly used to resolve addresses.The Global Offset Table (or GOT) is a section inside of programs that holds addresses of functions that are dynamically linked. As mentioned in the page on calling conventions, most programs don’t include every function they use to reduce binary size. Instead, common functions (like those in libc) are “linked” into the program so they can be saved once on disk and reused by every program.
So, we get the address of PLT and GOT puts
i.e. 0x400520
and 0x600c50
respectively.
The Gadget
For finding a gadget, I will use radare2’s /R < instruction >
command to find a pop rdi; ret;
gadget.
[0x00400590]> /R pop rdi |
Let’s copy the address i.e. 0x400853
.
Let’s make the exploit:-
First off, we have pop_rdi
gadget address and PLT and GOT puts
address.
from pwn import * # importing fuctions |
This script pretty much explains everything with comments. So, let’s see what we get after running this:-
[+] Starting local process './bitterman': pid 3117 |
Great, we now have the leaked address, we need to find the offset which will be constant for every address. So, let’s start carfting our ret2libc attack.
Stage 2: Final Exploit using ret2libc
So, from the previous exploit we get the leaked address. Now we need to find the offset and execute the /bin/sh
to spawn a shell.
First off, we need main
address because the leak address will be changed everytime so we need to get main address so we can use the leaked address to get the offset for current process.
Using objdump -D bitterman | grep main
, we will grab the address of the bitteman’s main
.
-snip-- |
Nice, we have the main address. Let’s proceed further for crafting the exploit.
Warning: Before we proceeds, we need the
libc.so.6
of your machine. Copy it withcp //lib/x86_64-linux-gnu/libc.so.6 .
, it willcopy thelibc.so.6
to the working directory.
Now, we will automate the finding of addresses of put
and system
with pwntools but beforehand we need /bin/sh
address as well. To find that, let’s use strings strings -a -t x libc.so.6 | grep /bin/sh
, we will get the address of /bin/sh
from the libc.so.6
.
Let’s make the exploit:-
#!/usr/bin/python |
This will spawn a shell and hence you learned how to do a ret2libc attack.
In a nutshell, we used the leak address to find the offset and hence using the gadget we overwrite the instruction pointer while calling the system
and /bin/sh
hence spawning a shell.
|
Final Words
Thanks to @Lord_Idiot for helping me out with the stack alignment issue.
The MOVAPS issue
If you’re using Ubuntu 18.04 and segfaulting on a movaps instruction in buffered_vfprintf() or do_system() in the 64 bit challenges then ensure the stack is 16 byte aligned before returning to GLIBC functions such as printf() and system(). The version of GLIBC packaged with Ubuntu 18.04 uses movaps instructions to move data onto the stack in some functions. The 64 bit calling convention requires the stack to be 16 byte aligned before a call instruction but this is easily violated during ROP chain execution, causing all further calls from that function to be made with a misaligned stack. movaps triggers a general protection fault when operating on unaligned data, so try padding your ROP chain with an extra ret before returning into a function or return further into a function to skip a push instruction.
That was it. Until then, pwn.
Files
Bitterman: https://github.com/ctfs/write-ups-2015/raw/master/camp-ctf-2015/pwn/bitterman-300/bitterman