It's because of KASLR (address space layout randomization), it makes it near impossible to identify the correct pieces of code in memory to string together (before we have code execution, we have to rely on stringing existing bits of code together in a way that it loads our own code and jumps to it)
The reason it works in the browser is because of the javascript engine, which is a big attack surface for potential exploits, but not only that, all of the API is exposed, and we can abuse that to make it do what we want, so finding the right bits of code in memory is essentially taken out of the equation.