> Abstract—We show that it is possible to write remote stack buffer overflow exploits without possessing a copy of the target binary or source code, against services that restart after a crash. This makes it possible to hack proprietary closed-binary services, or open-source servers manually compiled and installed from source where the binary remains unknown to the attacker. Tra- ditional techniques are usually paired against a particular binary and distribution where the hacker knows the location of useful gadgets for Return Oriented Programming (ROP). Our Blind ROP (BROP) attack instead remotely finds enough ROP gadgets to perform a write system call and transfers the vulnerable binary over the network, after which an exploit can be completed using known techniques. This is accomplished by leaking a single bit of information based on whether a process crashed or not when given a particular input string. BROP requires a stack vulnerability and a service that restarts after a crash. We implemented Braille, a fully automated exploit that yielded a shell in under 4,000 requests (20 minutes) against a contemporary nginx vulnerability, yaSSL + MySQL, and a toy proprietary server written by a colleague. The attack works against modern 64-bit Linux with address space layout randomization (ASLR), no-execute page protection (NX) and stack canaries.
In this case you can just read the previous paper, which explains the caveat that PIE daemons utilizing ASLR must be so-called fork-only services--i.e. forked from a supervisor process. If they're not fork-only than you can't leak the address space layout.
Presumably (hopefully) some other common, multi-process network daemons have been refactored to exec child processes. As explained in the paper, nginx, which utilizes an M:N process model, was susceptible. Anybody know if nginx was refactored to fork+exec its worker processes rather than just fork? (EDIT: AFAICT via a cursory examination of ngx_master_process_cycle, ngx_start_worker_processes, and related the answer is, "no".)
Seems to be a fairly reasonable introduction to binary exploitation, but at the end it concludes with the dubious “NX is counterproductive”. Turning off execution for regions that you really didn’t intend to be code is a good idea in general; JIT code generation is a rare case and warrants extra consideration. There’s multiple reasons why we do it: the first is that it’s cheap, but it also prevents you from straight-up writing code and instead having to rely on ROP (which, these days, is protected against by CFI). It’s even more useful in the presence of ASLR, as a “relative stack jump” without knowing the full slide is often easier than locating libc. It’s certainly not security theater like the article suggests; there is a reason why we have been investigating in further protections (shadow stacks, pointer authentication, etc) to further segregate code from data and even data from different kinds of data.
> Executable space protection requires special hardware (the NX bit) or expensive software emulation.
"Special hardware" is literally everywhere. It's more accurate to call it "special" if it doesn't support NX.
> Somebody must have thought so, because it is so prevalent now.
FWIW, those people (pippac iirc/ paxteam?) were well aware of ROP when they created it. It's sad how hard it is to find the original sources these days, the docs are all so old - someone can maybe find security.txt?
> One may ask: if executable space protection is so easily circumvented, is it worth having?
> Somebody must have thought so, because it is so prevalent now. Perhaps it’s time to ask: is executable space protection worth removing? Is executable space protection better than nothing?
> We just saw how trivial it is to stitch together shreds of existing code to do our dirty work. We barely scratched the surface: with just a few gadgets, any computation is possible. Furthermore, there are tools that mine libraries for gadgets, and compilers that convert an input language into a series of addresses, ready for use on an unsuspecting non-executable stack. A well-armed attacker may as well forget executable space protection even exists.
> Therefore, I argue executable space protection is worse than nothing. Aside from being high-cost and low-benefit, it segregates code from data. As Rob Pike puts it:
> This flies in the face of the theories of Turing and von Neumann, which define the basic principles of the stored-program computer. Code and data are the same, or at least they can be.
> Executable space protection interferes with self-modifying code, which is invaluable for just-in-time compiling, and for miraculously breathing new life into ancient calling conventions set in stone.
This is an absolutely unnecessary and totally specious line of reasoning. I worked for 6.5 years on V8 and can say with strong confidence that 99.9% of remote execution vulnerabilities that we saw in the field were because of RW executable memory. We did a lot of work to make write-execute exclusion fly in V8 for JavaScript code, without a major performance penalty, and that massively reduced this attack surface. That only works because we could totally quiesce JavaScript code while the JIT modified executable memory and flipped permissions from RW to RX. Yet other attack surfaces remain. In particular, V8 still needs RWX JIT code for Wasm, because tiering and parallel compilation. Guess where attackers learned to get the one remaining source of RWX memory? Yeah, just make an empty Wasm module. And for the record, that doesn't mean they were exploiting bugs in the Wasm engine, just using it to get some memory that is RWX that they can overwrite through some other bug.
This article, though great on specific technical details about ROP, is terrible security advice. It is not theater to reduce attack surfaces. Take away an attack surface and yeah, attackers will find a new one. But it acts like a filter; if you make it 10x harder, you get 10x fewer exploits. It raises the bar.
And for the record, though I didn't look at every single one, I never did, in fact, see a ROP exploit against Chrome except as part of some hackathon or pwn-to-own. Attackers were just walking in the front door when you have RWX!
I'm not working on V8 anymore, but I doubt things will change until that last source of RWX is gotten rid of. It is particularly challenging for Wasm because many compilation threads are cranking in the background, tiering up execution, while the program actually runs. Until that is fully done, I don't expect that many actual ROP attacks will appear.
It felt too recent to me when I wrote it! I was probably subconsciously hoping for the correction. The technique was certainly circulating many years before any of the papers were released, since all I remember about the papers was people talking about how the technique was finally being written down. :)
> Abstract—We show that it is possible to write remote stack buffer overflow exploits without possessing a copy of the target binary or source code, against services that restart after a crash. This makes it possible to hack proprietary closed-binary services, or open-source servers manually compiled and installed from source where the binary remains unknown to the attacker. Tra- ditional techniques are usually paired against a particular binary and distribution where the hacker knows the location of useful gadgets for Return Oriented Programming (ROP). Our Blind ROP (BROP) attack instead remotely finds enough ROP gadgets to perform a write system call and transfers the vulnerable binary over the network, after which an exploit can be completed using known techniques. This is accomplished by leaking a single bit of information based on whether a process crashed or not when given a particular input string. BROP requires a stack vulnerability and a service that restarts after a crash. We implemented Braille, a fully automated exploit that yielded a shell in under 4,000 requests (20 minutes) against a contemporary nginx vulnerability, yaSSL + MySQL, and a toy proprietary server written by a colleague. The attack works against modern 64-bit Linux with address space layout randomization (ASLR), no-execute page protection (NX) and stack canaries.