Basics of pwn: Stack
Key concept
stack grows down to lower memory addresses. (00000000~ffffffff)
The stack is simply an area in RAM that was chosen to be the stack.
rsp register holds the addres in memory where the bottom of the stack resides. When something is pushed to the stack, esp decreases by 8. When pop instruction is executed, the value at esp increases by 8.
ebp contains the top of the curretn stack frame. A stack frame is the space used on the stack by a given function
Examples
Stack is primarily used for the followings:
- Storing function arguments
- Storing local variables
- Storing processor state between function calls.
Here is an example code to build a basic idea of stack.
#include <stdio.h>
void say_hi(const char * name) {
printf("Hello %s!\n", name);
}
int main(int argc, char ** argv) {
char * name;
if (argc != 2) {
return 1;
}
name = argv[1];
say_hi(name);
return 0;
}
It’s a simple function where it takes a argument and print it out.
assembler code for function main
Dump of assembler code for function main:
0x080488b3 <+0>: lea ecx,[esp+0x4]
0x080488b7 <+4>: and esp,0xfffffff0
0x080488ba <+7>: push DWORD PTR [ecx-0x4]
0x080488bd <+10>: push ebp
0x080488be <+11>: mov ebp,esp
0x080488c0 <+13>: push ecx
0x080488c1 <+14>: sub esp,0x14
0x080488c4 <+17>: call 0x8048900 <__x86.get_pc_thunk.ax>
0x080488c9 <+22>: add eax,0x90293
0x080488ce <+27>: mov eax,ecx
0x080488d0 <+29>: cmp DWORD PTR [eax],0x2
0x080488d3 <+32>: je 0x80488dc <main+41>
0x080488d5 <+34>: mov eax,0x1
0x080488da <+39>: jmp 0x80488f8 <main+69>
0x080488dc <+41>: mov eax,DWORD PTR [eax+0x4]
0x080488df <+44>: mov eax,DWORD PTR [eax+0x4]
0x080488e2 <+47>: mov DWORD PTR [ebp-0xc],eax
0x080488e5 <+50>: sub esp,0xc
0x080488e8 <+53>: push DWORD PTR [ebp-0xc]
0x080488eb <+56>: call 0x8048885 <say_hi>
0x080488f0 <+61>: add esp,0x10
0x080488f3 <+64>: mov eax,0x0
0x080488f8 <+69>: mov ecx,DWORD PTR [ebp-0x4]
0x080488fb <+72>: leave
0x080488fc <+73>: lea esp,[ecx-0x4]
0x080488ff <+76>: ret
End of assembler dump.
assembler code for function say_hi
Dump of assembler code for function say_hi:
0x08048885 <+0>: push ebp
0x08048886 <+1>: mov ebp,esp
0x08048888 <+3>: push ebx
0x08048889 <+4>: sub esp,0x4
0x0804888c <+7>: call 0x8048900 <__x86.get_pc_thunk.ax>
0x08048891 <+12>: add eax,0x902cb
0x08048896 <+17>: sub esp,0x8
0x08048899 <+20>: push DWORD PTR [ebp+0x8]
0x0804889c <+23>: lea edx,[eax-0x2cd94]
0x080488a2 <+29>: push edx
0x080488a3 <+30>: mov ebx,eax
0x080488a5 <+32>: call 0x804f8e0 <printf>
0x080488aa <+37>: add esp,0x10
0x080488ad <+40>: nop
0x080488ae <+41>: mov ebx,DWORD PTR [ebp-0x4]
0x080488b1 <+44>: leave
0x080488b2 <+45>: ret
End of assembler dump.
You can see that at 0x080488e8 the stack pushes [ebp-0xc] where it pushes the argument for the binary.
and then creates a stack frame from 0x08048885 then prints out the name then leave which it moves ebp in to esp and pops the saved EBP. Then ret pops the saved instruction pointer into eip which causes the program to move back to the main function.