To be completely anal, RET can't be
entirely emulated in x86, as it is impossible to pop a value into EIP. You could always
MOV EDX, [ESP]
ADD ESP, 4
JMP EDX
but that involves the use of a general-purpose register (in this case EDX). If you needed to preserve all the registers' contents, which is uncommon but feasible, you'd come unstuck. Anyway...
What you've described is a stack-overflow exploit, which was previously the hack-of-choice on Windows XP. Such exploits are much harder to pull off since SP2 though as the stack can no longer be executed under any circumstances. So you really have to get creative when coming up with some shellcode to 'execute arbitrary code', as the phrase goes.
There are many other
types of exploit though. Heap overflows are similar to stack overflows, and have similar potential. Format-string attacks are also common, and have rendered millions of C programmers very dangerous, as they all seem to love printf() and friends. The web is rich with discussion on common exploits, so check it out some time.
I think the main reason the stack grows downwards is compatibility. Many programmers and compilers just got comfortable with the fact that local variables live at [EBP+], arguments at [EBP-] and that you could 'get away with' hacking the stack by manually pulling pieces of data from where they 'should' be. If, one day, the return value no longer lived at [EBP-4] then all hell would break loose.
It also makes things that little bit easier on the compiler, knowing that a page-fault at 0x12D000 is necessarily a stack-overflow. With compilers choosing different stack sizes for different applications, the guard-page would need to be put at a different place in lomem for each process.
Besides, when you list memory increasing downwards (as we all do) it's pleasing to see the stack growing 'up the page' [smile].
Regards
Admiral