Linux - ELF64 ROP leaks
Overview
In 64-bit architecture, the RIP
register is the instruction pointer register, equivalent to the 32-bit x86 architecture's EIP register.
Preliminary checks
NX and ASLR
A ROP exploit should only be used under certain circumstances, as more easier and straightforward techniques could potentially be used to exploit a buffer overflow vulnerability.
A ROP is needed whenever the kernel on which the binary is executed is making use of executable-space protection mechanisms
, preventing execution of certain memory regions in the stack by the processor. Indeed, if the mechanism is activated, any added shell code in the stack through the buffer overflow could not be directly executed. On Linux systems, the NX
bit (no-execute bit) is responsible for the activation of this mechanism.
Another protection mechanism protecting against the exploitation of buffer overflow vulnerability is the address space layout randomization (ASLR)
. In order to prevent reliable jumping to particular exploited function in memory, ASLR randomly arranges the address space positions of key data areas of a process, including the base of the executable and the positions of the stack, heap and libraries. With ASLR
activated it is thus not possible to jump at the memory address of a shell code placed in the stack or at a pre determined function, such as system
in the libc
.
The technique presented in this note should only be used whenever both NX
and ASLR
mechanisms are being used.
The rabin2
utility can be used to retrieve the protection mechanisms of a binary:
Printing functions
The exploitation technique presented in this note premised on the fact that addresses contained in the libc
can be leaked. A function allowing a display on stdout
is therefore necessary and must be present in the binary. The puts
and write
functions can be used to this effect for example.
The objdump
Linux can be used to disassemble a binary and display the plt
section, which contains references to external functions called by the binary.
At least one of the functions above must be present in order to conduct a ROP exploit based on libc
addresses leaks.
Calculate padding
The first step is to determine the needed number of bytes to overflow the injected buffer in order to overwrite the rip
instruction pointer register.
The pattern_create.rb
and pattern_offset.rb
ruby scripts of the Metasploit
framework can be used to calculate this offset.
If the binary is provided, gdp
can be used to retrieve the value contained in the rsp
registry after the crash. If the binary is only accessible remotely and can not be debugged, TODO
Leak libc address
Once the padding needed to overwrite rip
is known, the next step is to leak the address of a libc
function in the GOT
table.
Pass argument to the printing function
On 64-bit architecture, the rdi
register is used to pass the first argument to the called function. A gadget popping a value from the stack into the rdi
register is thus needed to specify what should be printed as an argument to the printing function.
The r2
utility can, among others, be used to find a pop rdi
gadget:
If the printing function takes more than one argument, gadgets to populate the following registers must be found:
%rsi
: 2nd argument ;%rdx
: 3rd argument ;%rcx
: 4th argument ;%r8
: 5th argument ;%r9
: 6th argument.
Printing function PLT and GOT entries
The entries in the PLT
and GOT
tables for the printing function must be retrieved, which can be done by disassembling the plt
entry of the binary with the objdump
Linux utility:
Return to main
To continue the program execution flow after printing the GOT
entry in libc
, in order to avoid the re randomization of the address space, a return at the beginning of the program, the main
function, is needed. It simply consist in adding the main
function address in the ROP chain after calling the printing function. This mechanism is called ret2main
.
The address of the main
function in the text
section can found using the objdump
Linux utility:
Stage 1 payload
Once the needed addresses have been retrieved, the following ROP chain can be constructed: junk + pop_rdi + printing_func_got + printing_func_plt + main_plt
.
The following Python snippet can be used to generate the payload:
Automated parsing of the leaked address
The address of the printing function should be leaked and printed on stdout
by the process. This value must be retrieved and stored in a variable for future use, as its needed to calculate the libc
randomized offset.
pwntools
can be used to automate the process:
The process can also be done using only Python built-ins, for example if the binary exploitation is made using a subprocess
:
Commands execution
The leaked printing function address can be used to retrieve the actual address of the libc
functions, such as system
in order to call, for example, /bin/sh
, and achieve code execution through the exploited binary.
Find libc symbols offsets
Retrieve libc symbols offsets with access to the libc
If an access to the libc
loaded by the exploited binary is possible, the symbol offsets of the libc
can easily be retrieved. Note that the libc
is usually located in the lib
folder: /lib/x86_64-linux-gnu/libc.so.6
.
Retrieve libc symbols offsets without access to the libc
If no direct access to the libc
is possible in the exploitation context, the symbols offsets of the libc
being used by the process can still be retrieved. Indeed, the libc-database
can be used to determine the libc
loaded given two entries of the GOT
table. Search queries to the database can be make using https://libc.blukat.me/
.
A second entry in the GOT
table must be obtained:
Calculate randomized libc addresses
Once the symbols offsets of the libc
being used have been retrieved, the randomized libc
addresses of the process can be calculated using the leaked printing function address:
Specificities of SUID binaries
In case of exploiting an SUID
binary for local privilege escalation, the privileges granted by the SUID
bit may be dropped. Notably libc
includes security mitigations whenever calling system()
with /bin/sh
that drops the privileges if euid
(process effective user identifier) != uid
(user identifier). In case of an SUID
binary, the euid
corresponds to the uid
of the binary owner.
A call to setuid
can be used before calling system("/bin/sh")
to change the uid
of the process to the one of the binary owner, usually 0
in case of privilege escalation to root
.
Stage 2 payload
Once the randomized addresses of system
and /bin/sh
in libc
are obtained, the following ROP chain can be used to achieve code execution:
with out
setuid
:junk + pop_rdi + sh + sys
with
setuid
, forSUID
binaries,junk + pop_rdi + 0x<EUID> + setuid + pop_rdi + sh + sys
pwntools final code template
The following exploit code can be used as a template to exploit ROP buffer overflow vulnerability with libc
address leaks:
Last updated