Uplink: From a scanf Overflow to system() via a Heap Task Overwrite
Every modern mitigation is on, so control-flow hijacking is off the table. The path to system() is a data-only chain: an unbounded scanf corrupts a size field, which turns a later read() into a heap overwrite of the task the program is about to execute.
Flag and live connection details are redacted. Exploitation was validated locally in a containerized replica.
The binary
checksec on the provided ELF showed a fully hardened target:
Full RELRO · Stack canary · NX · PIE · SHSTK · IBT
That combination rules out the usual stack-smash-to-shellcode or simple return-address overwrite. The symbols were intact (main_menu, run_diagnostics, view_telemetry, ...) and the imports included system, read, scanf, and malloc, which hinted the intended path was data corruption, not control-flow hijack.
The bug chain
Reversing main_menu in Ghidra revealed a stack layout where the operator callsign sits in a 16-byte buffer and a transmit size, TX_SZ, sits right after it at offset +0x10:
- Overwrite the size field. Menu option 1 reads the callsign with an unbounded
scanf("%s"). A 20-byte callsign overruns the 16-byte buffer and overwritesTX_SZ. - Turn the bad size into a heap overflow.
current_packetis amalloc(0x20)heap buffer;current_taskis the next relevant allocation. Menu option 3 uses the now-corruptedTX_SZas the length for aread()intocurrent_packet, so the oversized read spills out of the packet and overwritescurrent_task. - Reach system().
run_diagnosticscallssystem(current_task)when an "enabled" flag atcurrent_task + 0x40is non-zero. Shaping the overwrite so the task points at an attacker-controlled command with that flag set turns the diagnostic into command execution.
A pwntools script drives the menu in order (set oversized callsign, trigger the over-long read, fire diagnostics) and was validated against a local Docker/amd64 replica with a placeholder flag file.
Takeaways
- Full mitigations do not mean "unexploitable." When canaries, NX, PIE, and CET block control-flow hijacking, look for data-only chains: corrupt a length, an index, a pointer, or a function argument.
- A one-field stack overflow (
scanf("%s")into a size variable) became the primer for a heap overwrite two menu options later. Bugs compound across program state. - Symbol-rich binaries reward reversing the data model, not just the call graph. The win here was understanding what
current_taskis and who callssystem()on it.