A couple minor corrections.
gcc -g -fno-stack-protector -z execstack vulnerable.c
gcc -g -fno-stack-protector -z execstack vulnerable.c -o vulnerable
Notice how it says rip at 0x7fffffffed18? That’s the address of the stored return address!
value should be 68 not 18.
When I tried running it I got this:
$ python3 exploit.py | env - setarch x86_64 -R ./vulnerable
*** buffer overflow detected ***: ./vulnerable terminated
Traceback (most recent call last):
File "exploit.py", line 30, in <module>
BrokenPipeError: [Errno 32] Broken pipe
I’m on nixos. Adding the flag -D_FORTIFY_SOURCE=0 fixed it and the exploit works.
Good catches, thanks! Fixed in https://github.com/jonhoo/thesquareplanet.com/commit/21347af0e2cd27692b96159c8dab695ed10415e6. I’ll also add a paragraph on -D_FORTIFY_SOURCE.
It’s a good article and a useful starting point, I would like to see follow ups with analysis and approaches when one or more of the security hardening flags are enabled.
In particular it may not be possible to exploit the example vulnerable program at all with modern security hardening added, programmers I talk to who have not studied exploitation do not really seem to understand this. All buffer overflows are thought of as the end of the world. It may need a more complex interactive vulnerable binary from which we can first extract secrets then build an exploit payload.
I wrote this as an accompanying text to MIT’s 6.858 Computer Security lab on buffer overflows, and in that lab we actually do have the students pull off a similar attack without -z execstack. Address randomization is harder, but doable through, for example, user-controlled format strings (though of course, there are also compiler checks that help mitigate that). The efficacy of stack canaries really depends on what kind of stack canaries are used, and the mechanism of attack. Terminator canaries for example are often fixed, and given a good attack vector you may be able to just blow right through them. Similarly, if you’re not doing just a classic buffer overflow, but something like a dangling pointer attack, then the canaries don’t help at all. D_FORTIFY_SOURCE similarly only helps when the compiler can guess appropriate bounds.
Overall, I think I’d say that if all compiler mechanisms are turned on, and the code is well written, it’s pretty darn hard to pull off an exploit. However, it’s so rare that both of those are true, especially in older software, and that’s why we end up with so many working exploits. In some sense, this is due to simple math: the attacker only needs one vulnerability, whereas the developer needs to ensure there are no vulnerabilities.
since it seems related: https://lobste.rs/s/1yi1op/smashing_stack_protector_for_fun_profit
Ah, yes, that looks like another interesting take on how to defeat stack canaries! Thanks for sharing.