Description
Can you handle function pointers?
Setup
Download chall and chall.c for local reversing.
Connect to mimas.picoctf.net <PORT_FROM_INSTANCE> to exploit the live service.
wget https://artifacts.picoctf.net/c_mimas/51/chall && \
chmod +x chall && \
wget https://artifacts.picoctf.net/c_mimas/51/chall.cnc mimas.picoctf.net <PORT_FROM_INSTANCE>Solution
- Step 1Find win()Use objdump -D chall | grep win to note the win() address (0x4011a0). Because the program runs on little-endian amd64, the payload must use the reversed byte order.
objdump -D chall | lessLearn more
A win function (also called a "magic function" or "backdoor function") is a function in the binary that prints the flag or grants access - but is never called during normal program execution. It exists only to be reached via exploitation. Finding it with
objdump | grep winor in Ghidra's function list is the first step.objdump -Ddisassembles the entire binary, including all sections. The-dflag (lowercase) only disassembles executable sections, but-Dshows everything including data sections, which can reveal embedded strings and constants. Piping throughgrep winquickly finds the function name in the symbol table output.The win() function's address (
0x4011a0in this build) is the value you need to write to the function pointer. Since the binary is compiled without PIE (Position-Independent Executable), this address is fixed - it doesn't change between runs. If PIE were enabled, you'd need to leak the base address first (via a format string or info disclosure bug) and compute the win() address at runtime.On a real binary without a win() function, you would instead redirect execution to a ROP gadget or shellcode. The win() function is a training-wheels simplification used in CTF challenges to focus learning on the overflow mechanic without requiring knowledge of ROP chains.
- Step 2Craft the payloadOverflow the 32-byte buffer with filler followed by the little-endian win() pointer (\xa0\x11\x40\x00\x00\x00\x00\x00).
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xa0\x11\x40\x00\x00\x00\x00\x00Learn more
Little-endian byte order means the least significant byte is stored first. The address
0x4011a0in little-endian 64-bit representation is\xa0\x11\x40\x00\x00\x00\x00\x00(lowest byte first). When the program reads this 8-byte sequence from the heap as a 64-bit pointer, it reconstructs0x00000000004011a0- the correct function pointer value.Getting byte order right is one of the most common sources of bugs in exploit development. pwntools provides
p64(address)to pack a 64-bit value in little-endian format andp32(address)for 32-bit values. Using these helpers avoids manual byte reversal errors, especially for addresses with zeros in unexpected positions.The 8-byte pointer size is specific to 64-bit systems (x86-64, arm64). On 32-bit systems, pointers are 4 bytes and addresses fit in a single word. When exploiting a binary, always verify the word size - it affects pointer sizes, stack alignment requirements, and calling conventions.
Function pointer overwrites were historically one of the most powerful heap exploit primitives. By overwriting a function pointer stored on the heap (like a callback, a vtable entry, or a longjmp buffer), an attacker redirects execution to arbitrary code the next time the pointer is called. Modern mitigations like CFI (Control Flow Integrity) and CET (Control-flow Enforcement Technology) restrict where function pointers can jump, limiting this attack class.
- Step 3Automate the menu interactionSend option 2, deliver the payload, then call option 4 to execute the overwritten function pointer and print the flag.
echo -e -n "2\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xa0\x11\x40\x00\x00\x00\x00\x00\n4\n" | nc mimas.picoctf.net <PORT_FROM_INSTANCE>A short pwntools script can accomplish the same thing if you prefer Python automation.Learn more
The
echo -e -ntechnique passes the entire interaction sequence as a single string piped to netcat. The-eflag enables escape sequences (so\nbecomes a newline and\xa0becomes the byte 0xa0) and-nsuppresses the trailing newline thatechonormally adds. This non-interactive mode is useful for scripting and automation.However, non-interactive delivery has a pitfall: if the server adds artificial delays between menu prompts, the entire input may be buffered and sent before the server is ready. pwntools handles this gracefully with
p.sendline()andp.recvuntil()calls that synchronize with the server's responses. For robust exploit delivery, pwntools scripts are preferred over pipe-to-netcat one-liners.The menu option sequence (2 → write payload → 4 → trigger) demonstrates how exploit steps must be sequenced correctly. In a complex exploit chain, the ordering of steps is critical: attempting step 4 before step 2 would fail silently or cause unintended behavior. Careful sequencing, combined with response verification at each step, is the hallmark of reliable exploit automation.
Once win() executes, it typically calls
system("/bin/sh")or directly reads and prints the flag file. Either way, execution has been redirected to attacker-chosen code - which is the definition of arbitrary code execution. From this point, the attacker has the same capabilities as the program (read files, make network connections, etc.), subject to OS-level permissions.
Flag
picoCTF{and_down_the_road_we_go_dbb...}
Overwriting the function pointer with win() immediately prints the flag.