Well to start off with the obvious, the stack is the primary location for arguments of functions under the _STDCALL_ declaration (under x86 this is true, amd64 call method uses a few registers first, then uses the stack for any additional arguments, but I digress).
Therefore:
MessageBoxA(NULL, "Text!", "Caption!", MB_OK);
Turns into:
push MB_OK
push OFFSET Caption
push OFFSET Text
push 0
call <&user32.MessageBoxA>
So as you can see, its primary purpose is the conveyance of arguments, but what else is its purpose?
Well it also sets up the stack frame for any local arguments within a function. So let's say we have this slice of code.
#include <stdio.h>
#include <stdlib.h>
int SimpleFunction(int);
int main(int argc, char* argv[])
{
printf("5+5: %d\n10+5: %d", SimpleFunction(5), SimpleFunction(10));
exit(0);
}
//Simply add 5 to the number and return the number
int SimpleFunction(int n)
{
//Store n + 5 into a local variable
int val = n + 5;
return val;
}
Now let's compile this source, and view it in OllyDbg to get it's full effect!
So here is our disassembled SimpleFunction to assembler, which isn't a whole lot of code, but that's alright!
So we are sitting on execution before even pushing ebp. So you'll get the full effect!
Here is our stack frame at our current point of execution.
So seeing as how the stack frame still needs to be set up for this call, we are currently pointing to our return address.
Our first instructions are push ebp, which merely stores the prior stack frame pointer onto the stack for later retrieval, as ebp is used to refer to any arguments and local variables once inside the local frame. mov ebp,esp stores the current stack pointer into ebp, and sub esp,0x10 merely subtracts 16 bytes from esp for argument's use if the function per-chance has more function calls in it. When ebp is saved, this is the reason why it is done. Let's say a() calls b() inside of it, we still have to have access to our local variables and the like upon re-entry to a() after b() completes! This was the idea of pushing the old ebp value and it being restored on exit either via an add esp,XX and pop ebp, or as you will see in the image, a leave instruction. This simply readjusts the stack and restores ebp for you.
Now less ranting and more images!
So this is the image of the stack after the instructions push ebp; mov ebp,esp; sub esp,0x10;
As you can see, we now have space on our stack for local variables! The register ebp is pointing to 0x0060fe08, which means that [ebp+4] would be none other than our return address, and [ebp+8] would be our argument! It can usually be stated, that usually [ebp] is the old ebp value for the prior stack frame, [ebp+4] is the return address, and anything [ebp+XX] where XX is a value 8 or higher (in increments of 4) is an argument being accessed. This is of course assuming you are using 32 bit system of course :).
Now let's take a look at what happens on the flip side of things! When things like [ebp-4] are accessed! In the code above, there are two instructions that access [ebp-4], and those are at 0x004013f8 and 0x004013fb. What [ebp-4] is actually accessing is the local variable val!
So to put a long story short, a stack frame is created anytime a call is entered, and the prior stack frame is preserved upon entering another function call!
If we have functions a(), b(), and c() and they are called in their respective order, the stack frame will end up looking like this! (also please excuse my poor drawing skills if you'd be so kind, thank you)
[[edit: YIKES! I forgot that the current address of ebp in a stack frame actually points to it's old value, not the RET ADDR! Sorry folks! Just pretend like I got everything right the first time :). Funny considering I got it right in explaining [ebp+4] up above...Oh well!]]
Remember, that even though the stack grows up, it actually grows down. Yes I said down! The addresses for the stack actually decrease as the stack grows up. (This is a setting in the Intel processor in the GDT entry table, which can have a granular data entry to grow down, but all that at a later time!)
The stack frame bit on the very top of my horribly done drawing is a top down look at the stack frame, in the same view as the stack (grows up, but addresses decrease). The esp address actually is set either manually (push ebp; mov ebp,esp; sub esp,XX) or can be done via an enter instruction (which takes care of the stack manipulation to enter a stack frame automatically making it less bytes instruction wise, but is about double the clock cycles of manually setting it up). However upon execution of the call instruction, the RET ADDR is auto-magically pushed onto the stack, and to speak of the arguments...well those were placed their by either you or your compiler :).
Well that concludes this part of The Stack! (Leave your hammer at home)! Just getting a basic feel for the stack is super important for x86 (and I can also say a few other assembler languages too :P), and especially exploit development. The stack is a tricky mistress at first, but follows laws of logic much like Newtonian laws of physics.
So long, and until next time folks!