1. Introduction

This article describes the stack. GDB is used to analyze its memory. One needs to know this subject to play with low-level security.

Environment: x86, Linux, GCC, GDB.

2. Registers

The following registers are mentioned in the article:

  • ESP (points to the top of the stack)
  • EBP (is used as a reference when accessing local variables and arguments of the function)
  • EIP (points to the address of the next instruction)

3. Stack

When the function is called, the following items are pushed on the stack (in the order of appearance):

  • arguments of the function (in reverse order)
  • return address
  • current EBP

Then, local variables of the function are pushed on the stack.

4. Program

A simple program was written in C in order to analyze the stack. Two numbers are sent to the function, which adds them and returns their sum.

#include

int add_numbers(int n1, int n2)
{
     int sum=n1+n2;
     return sum;
}

int main()
{
     int n1=1;
     int n2=2;
     int sum;

     sum=add_numbers(n1,n2);
     printf("The sum of 1 and 2 is %dn",sum);

     return 0;
}

Let’s compile the code.

dawid@lab:~$ gcc -g stack_analysis.c

5. Assembly code

Let’s start GDB and analyze the assembly code of main() and add_numbers():

dawid@lab:~$ gdb -q ./a.out
Reading symbols from /home/dawid/a.out...done.
(gdb) disass main
Dump of assembler code for function main:
   0x080483fb <+0>:	push   ebp
   0x080483fc <+1>:	mov    ebp,esp
   0x080483fe <+3>:	and    esp,0xfffffff0
   0x08048401 <+6>:	sub    esp,0x20
   0x08048404 <+9>:	mov    DWORD PTR [esp+0x1c],0x1
   0x0804840c <+17>:	mov    DWORD PTR [esp+0x18],0x2
   0x08048414 <+25>:	mov    eax,DWORD PTR [esp+0x18]
   0x08048418 <+29>:	mov    DWORD PTR [esp+0x4],eax
   0x0804841c <+33>:	mov    eax,DWORD PTR [esp+0x1c]
   0x08048420 <+37>:	mov    DWORD PTR [esp],eax
   0x08048423 <+40>:	call   0x80483e4 <add_numbers>
   0x08048428 <+45>:	mov    DWORD PTR [esp+0x14],eax
   0x0804842c <+49>:	mov    eax,0x8048510
   0x08048431 <+54>:	mov    edx,DWORD PTR [esp+0x14]
   0x08048435 <+58>:	mov    DWORD PTR [esp+0x4],edx
   0x08048439 <+62>:	mov    DWORD PTR [esp],eax
   0x0804843c <+65>:	call   0x804831c <printf@plt>
   0x08048441 <+70>:	mov    eax,0x0
   0x08048446 <+75>:	leave
   0x08048447 <+76>:	ret
End of assembler dump.
(gdb) disass add_numbers
Dump of assembler code for function add_numbers:
   0x080483e4 <+0>:	push   ebp
   0x080483e5 <+1>:	mov    ebp,esp
   0x080483e7 <+3>:	sub    esp,0x10
   0x080483ea <+6>:	mov    eax,DWORD PTR [ebp+0xc]
   0x080483ed <+9>:	mov    edx,DWORD PTR [ebp+0x8]
   0x080483f0 <+12>:	lea    eax,[edx+eax*1]
   0x080483f3 <+15>:	mov    DWORD PTR [ebp-0x4],eax
   0x080483f6 <+18>:	mov    eax,DWORD PTR [ebp-0x4]
   0x080483f9 <+21>:	leave
   0x080483fa <+22>:	ret
End of assembler dump.

This assembly code will be referenced in the article.

6. Breakpoints

Let’s add some breakpoints.

(gdb) list main
1	#include
2
3	int add_numbers(int n1, int n2)
4	{
5		int sum=n1+n2;
6		return sum;
7	}
8
9	int main()
10	{
11		int n1=1;
12		int n2=2;
13		int sum;
14
15		sum=add_numbers(n1,n2);
16		printf("The sum of 1 and 2 is %dn",sum);
17
18		return 0;
19	}
(gdb) break 15
Breakpoint 1 at 0x8048414: file stack_analysis.c, line 15.
(gdb) break add_numbers
Breakpoint 2 at 0x80483ea: file stack_analysis.c, line 5.
(gdb) break 6
Breakpoint 3 at 0x80483f6: file stack_analysis.c, line 6.
(gdb) break 16
Breakpoint 4 at 0x804842c: file stack_analysis.c, line 16.

Breakpoint 1: set before pushing the arguments of add_numbers() on the stack

Breakpoint 2: set after the prolog of add_numbers(). The prolog is:

   0x080483e4 <+0>:	push   ebp
   0x080483e5 <+1>:	mov    ebp,esp
   0x080483e7 <+3>:	sub    esp,0x10

Breakpoint 3: set before leaving add_numbers()

Breakpoint 4: set after leaving add_numbers().

Between breakpoints 3 and 4 the epilog of add_numbers() is executed. The epilog is:

   0x080483f9 <+21>:	leave
   0x080483fa <+22>:	ret

7. Breakpoint 1 – analysis

Let’s run the program and analyze ESP, EBP and EIP.

(gdb) run
Starting program: /home/dawid/a.out

Breakpoint 1, main () at stack_analysis.c:15
15		sum=add_numbers(n1,n2);
(gdb) i r esp ebp eip
esp            0xbffff420	0xbffff420
ebp            0xbffff448	0xbffff448
eip            0x8048414	0x8048414 <main+25>

ESP is smaller than EBP, because the stack grows in the direction of smaller addresses. As it can be seen in the assembly code, EIP points to pushing on the stack the second argument of add_function().

Please notice that the next instruction after leaving add_numbers() is at the address 0×08048428 (see the assembly code). This is the return address.

8. Breakpoint 2 – analysis

Let’s continue the program and check ESP, EBP and EIP after the prolog of add_numbers(). Moreover, let’s analyze the memory starting from the top of the stack in the direction of higher addresses.

GDB-figure-8

As it can be seen in the underlined code, the following items have been pushed on the stack (in the order of appearance):

Want to learn more?? The InfoSec Institute Advanced Hacking course aims to train you on how to successfully attack fully patched and hardened systems by developing your own exploits. You will how to circumvent common security controls such as DEP and ASLR, and how to get to confidential data. You take this knowledge back to your organization and can then formulate a way to defend against these sophisticated attacks. Some features of this course include:
  • Create 0day attacks as part of the Advanced Persistent Threat
  • 5 days of Intensive Hands-On Labs
  • Use fuzzers and dynamic analysis to attack custom and COTS apps
  • Reverse engineer binaries to find new vulnerabilities never discovered before
  • Attack and defeat VPNs, IDS/IPS and other security technologies

- 0×00000002 (second argument of add_numbers())

- 0×00000001 (first argument of add_numbers())

- 0×08048428 (address of the next instruction after leaving add_numbers() – the return address)

- 0xbffff448 (current EBP – the one from main())

After pushing current EBP on the stack, ESP has been copied to EBP, which is used as a reference in add_numbers() when accessing arguments and local variable of this function (see the assembly code).

EIP points to the address of the next instruction after the prolog of add_numbers().

9. Breakpoint 3 – analysis

Let’s continue the program and analyze the memory before leaving add_numbers().

GDB-figure-9

In the meantime, the sum of arguments of add_numbers() has been calculated and pushed on the stack (underlined).

10. Breakpoint 4 – analysis

Let’s continue the program and analyze ESP, EBP, and EIP after leaving add_numbers().

(gdb) cont
Continuing.

Breakpoint 4, main () at stack_analysis.c:16
16		printf("The sum of 1 and 2 is %dn",sum);
(gdb) i r esp ebp eip
esp            0xbffff420	0xbffff420
ebp            0xbffff448	0xbffff448
eip            0x804842c	0x804842c <main+49>

In the meantime, the epilog of add_numbers() has been executed and the control returned to main(). EBP has been popped off the stack and points to the previous address (the one before calling add_numbers() – see breakpoint 1). The return address (0×08048428) has also been popped off the stack and the instruction at this address was executed. EIP points to the address of the next instruction. ESP points to the previous address (the one before calling add_numbers() – see breakpoint 1).

11. Summary

This article described the stack. GDB was used to analyze its memory. The intention was to write an introductory text for those who want to study how buffer overflow works.