The mistake looks harmless:
printf(user_input); // should have been printf("%s", user_input)
If user_input reaches the format argument, the attacker controls the format
string — and printf will happily walk the stack for conversions that were
never supplied.
Read
Feed it %p %p %p %p and each specifier pops the next stack slot, leaking
pointers, canaries, and saved return addresses. %s dereferences a slot as a
char *, so with a controlled pointer on the stack you get an arbitrary read.
Write
%n writes the number of bytes printed so far to the pointed-at address. Pair
it with width specifiers to set the count precisely and you have an arbitrary
4-byte write — enough to overwrite a GOT entry, a return address, or a
function pointer. %hn / %hhn narrow it to 2 or 1 bytes for surgical edits.
Why it still matters
Modern -Wformat-security and FORTIFY catch the obvious cases, but they only
fire when the compiler can see the literal. Wrap the call behind a logging
helper, or build the format string at runtime, and the check goes quiet while
the primitive stays live.
Rule of thumb: the format string is code. Never let the user supply it.
More PoCs and target write-ups to follow. Send yours — working code only.