If the stack grows downward, functions that are called later get stack frames at lower memory addresses. Also, the return address is pushed to the stack before space for local variables is reserved, so the return address gets a higher address than the local variables. But arrays and buffers are still indexed upwards in memory, so writing past the end of the array will nicely hit the return address next on the stack.
Example, with mandatory ASCII art:
Consider the trivial function that takes input from an untrusted source, and copies it to a local buffer:
void foo(char *s)
{
char buf[8];
strcpy(buf, s);
return;
}
The stack looks a bit like this:
<---- stack grows to the left
memory addresses increase to the right -->
0x8000 0x8010
+--------+----------+---------++------------
+ buf[8] | ret addr | char *s || .......
+--------+----------+---------++--------------
<--------- foo() -----------> <---- caller --
The stack is filled from right to left, starting with the function arguments, then the return address, then the locals of the function. It's easy to see that a simple overflow from buf
towards increasing addresses will hit the return address nicely.
So, what if the stack is inverted, and it grows upwards? Then overflowing a buffer will run the same way the stack grows, towards the empty part of the stack.
Sounds nice, but it doesn't help if foo()
calls another function to do the copy. Which isn't unusual, I just did that with the strcpy
. Now the stack looks like this:
stack grows to the right -->
memory addresses increase to the right -->
0x8000 0x8010
------------++---------+----------+---------++-----------+-------------+
.... || char *s | ret addr | buf[8] || ret addr | locals ... |
------------++---------+----------+---------++-----------+-------------+
caller ---> <-------- foo() -------------> <---- strcpy() ---------->
Now, overrunning (to the right) the buffer in foo()
's stack frame will nicely overwrite the return address of strcpy()
, not foo()
. Doesn't matter, though, we still jump to a location set by the overflowing, attacker-controlled data.