Definition
Purpose
A running program needs a place to store things in memory.
This place in memory is called the stack, where the program stores temporary variables.
When you run your program, the different parts (sections) of your binary file will be mapped into memory, and will look like this :
.text: Contains the code of the program.
.data: Contains the global and static variables initialized by the program.
.bss: Contains statically allocated variables represented exclusively by 0 bits.
Heap:
- Stores dynamically allocated variables, by functions like
malloc()for example. - Grows to the higher memory addresses (down in the diagram)
Stack:
- Stores temporary variables
- Grows to the lower memory addresses (up in the diagram)
- Follows LIFO order: Last In First Out
GDB example
Simple example with integers
Let's take this simple example.
int main(int argc, char *argv[])
{
int a = 4;
int b = 5;
return a + b;
}
Compile it with gcc:
gcc main.c -m32 -o mainThen run it inside gdb :
gdb mainPut a breakpoint on the function
main()pwndbg> b mainThen run the binary
pwndbg> r # short for runStep forward until you get to the instruction
0x565561a3 <main+38> leavepwndbg> ni # short for next instructionDisplay the stack
pwndbg> stack
00:0000│ esp 0xffffd298 ◂— 0x0 # top of the stack
01:0004│ 0xffffd29c ◂— 0x0
02:0008│ 0xffffd2a0 ◂— 0x5 # int b
03:000c│ 0xffffd2a4 ◂— 0x4 # int a
04:0010│ ebp 0xffffd2a8 ◂— 0x0 # bottom of the stack
The variables are stacked on top of each other, in the order they are seen.
Since int a appears first in the code, it is put on the stack first.
Then int b.
Example with strings
#include <stdio.h>
int main(int argc, char *argv[])
{
char buffer[20] = {0};
printf("Hello, send some stuff please !\n");
gets(buffer);
return 0;
}
Compile the code, then run into it with gdb.
Inside gdb, disassemble the
main()function.pwndbg> disassemble main
Dump of assembler code for function main:
0x5655619d <+0>: lea ecx,[esp+0x4]
0x565561a1 <+4>: and esp,0xfffffff0
0x565561a4 <+7>: push DWORD PTR [ecx-0x4]
0x565561a7 <+10>: push ebp
0x565561a8 <+11>: mov ebp,esp
...
0x565561f6 <+89>: call 0x56556040 <gets@plt>Put a breakpoint on the call to
gets(), which should be on offset +89.pwndbg> b *main + 89Run the program, which will stop on the breakpoint
pwndbg> rGo to the next instruction with
ni, gdb will ask for your input.- Type
aaaa
- Type
Now check the stack
pwndbg> stack
00:0000│ esp 0xffffd260 —▸ 0xffffd27c ◂— 'aaaa'
01:0004│ 0xffffd264 ◂— 0x0
02:0008│ 0xffffd268 —▸ 0xf7c1ca2f ◂— '_dl_audit_preinit'
03:000c│ 0xffffd26c —▸ 0x565561b4 (main+23) ◂— add ebx, 0x2e40
04:0010│ 0xffffd270 —▸ 0xf7fc14a0 —▸ 0xf7c00000 ◂— 0x464c457f
05:0014│ 0xffffd274 —▸ 0xf7fd98cb (_dl_fixup+235) ◂— mov edi, eax
06:0018│ 0xffffd278 —▸ 0xf7c1ca2f ◂— '_dl_audit_preinit'
07:001c│ eax 0xffffd27c ◂— 'aaaa'
As you can see, on the first line, the value at the address 0xffffd260 is not a normal value, but an address.
It's a pointer to a string, 'aaaa', which is our user input.
So we can confirm that our string is indeed stored on the stack.