Exploit for the MCH2022 CTF badge challenge ("Hack Me If You Can"). A full walkthrough can be found at wallaby3.com/posts/mch2022-badge-challenge/.
- Observe behaviour
- Experiment with inputs.
- Observe output via TCP and debug console.
- Identify likely buffer overflow at inputs >48 bytes.
- Analyze binary
- Use Ghidra with an open source Xtensa module for disassembly.
- Determine that flag is read from non-volatile storage and copied to a fixed address.
- Map execution flow
- Observe that certain buffer sizes >48 bytes will trigger a
gdbstub
register dump to the debug console due to a corrupted return address. - Use the register dump to follow execution flow in Ghidra. Determine
that the callee at the time our corrupted return address gets loaded
is the innermost call to
do_echo_recursive()
. The caller is the second innermost call. - Identify the offsets needed to set the return address (a0) and stack pointer (a1) of the callee's register window, and the registers a10 and a11 of the caller's register window.
- Observe that certain buffer sizes >48 bytes will trigger a
- Identify ROP gadget
- Use Ghidra to identify a
call8
instruction tolwip_write(int socket, void* data, size_t size)
. We can use this function to send data back to our client. Note we can do this without any setup because a TCP connection already exists at the time we hijack execution flow.
- Use Ghidra to identify a
- Determine ROP gadget arguments
- Use the register dump to observe that the socket descriptor (register a2
in the caller's register window) is always the same (
0x37
) and likely deterministic. Set a10 (socket
argument) to0x37
. - Set a11 (
*data
argument) to the flag location we found earlier. - Observe that register a12 (
size
argument) is reliably set to a large value and does not need to be modified.
- Use the register dump to observe that the socket descriptor (register a2
in the caller's register window) is always the same (
- Create malicious buffer and run exploit
./exploit.py target port