Skip to main content

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.

main.c
int main(int argc, char *argv[])
{
int a = 4;
int b = 5;
return a + b;
}
  • Compile it with gcc:

    gcc main.c -m32 -o main
  • Then run it inside gdb :

    gdb main
  • Put a breakpoint on the function main()

    pwndbg> b main
  • Then run the binary

    pwndbg> r       # short for run
  • Step forward until you get to the instruction 0x565561a3 <main+38> leave

    pwndbg> ni      # short for next instruction
  • Display 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

main.c
#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 + 89
  • Run the program, which will stop on the breakpoint

    pwndbg> r
  • Go to the next instruction with ni, gdb will ask for your input.

    • Type aaaa
  • Now check the stack

    pwndbg> stack
    00:0000esp 0xffffd260 —▸ 0xffffd27c ◂— 'aaaa'
    01:00040xffffd264 ◂— 0x0
    02:00080xffffd268 —▸ 0xf7c1ca2f ◂— '_dl_audit_preinit'
    03:000c│ 0xffffd26c —▸ 0x565561b4 (main+23) ◂— add ebx, 0x2e40
    04:00100xffffd270 —▸ 0xf7fc14a0 —▸ 0xf7c00000 ◂— 0x464c457f
    05:00140xffffd274 —▸ 0xf7fd98cb (_dl_fixup+235) ◂— mov edi, eax
    06:00180xffffd278 —▸ 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.