Functions and stack frames

HacksLand | The computer science playground

Posted by Thilan Dissanayaka on Aug 12, 2019

Stack is a concept used in computer architecture to store temporary data. It is used to pass argument data to functions and fetch return values. In "The stack architecture explained" tutorial we covered the theoretical background of the stack architecture. Before continue I would recommend you to read that document to get a basic idea about the stack. In this document we are going to see the practical usage of the Stack using a C program.

Also you can refer Introduction to C language tutorial to et some basic understanding on C programming, functions etc.

If you are not familiar with GDB , you can readprotostar stack0 tutorial.

Let's start. Hear you can see the source of C program we are using to build a binary. If you have some basic knowledge in C you can understand what's going on .

#include <stdio.h>

int testFunction(int a , int b , int c){
	char buff[40];
	int d = a + b + c ;
	return 0;
}	

int main(int argc, char const *argv[])
{
	testFunction(3,4,5);
	printf("after testFunction I'll be printed\n");
	return 0;
}

First it declare a function called testFunction . Actually testFunction does nothing. It gets 3 integer arguments as a,b and c. After a character buffer is declared. Finally assign the sum of a,b and c to integer d. Next in Main function we call testFunction with 3,4 and 5 as the arguments. In main function last thing to do is print a string using printf.

Let's compile the program and run it.

firstRunStack

Did you notice I used some additional commands for compile the program. we use command -g for give some additional information about the binary file to GDB. So we can list the source of binary in GDB. What means mpreferred-stack-boundary=2 ? . It tells GCC compiler to don't use stack alignment .If we don't use that command the assembly instructions will be more complex. So for now we use this command to understand things simply.

Now the time to disassemble the binary.

disassedMainandTFunc

Hear I used GDB for disassemble the binary. First of all I have set disassembly-flavor to Intel. Personally I like to use Intel's syntax for assembly. Because it's clear and well presented. In above SS you can see I listed the source code in blue box. After that I disassembled the main function [in red box]. Next you can see the disassemble of testFunction function [green box].

setaBreacPoint

After that I have set a break point at main+6. You can set a break point using the command break. To use a memory address as the argument for break command we use a asterisk '*' in-front of the memory address.

stacklook

As a yellow box you can see the stack frame of Main function. Don't worry too much about that. I'll explain step by step how a stack frame is building. when we call testFunction inside the main function , first thing to do is put argument for testFunction in stack.

mov    DWORD PTR [esp+0x8],0x5

This assembly instruction will copy hex value 5 into a place in stack. Yes it is esp + 0x8. You know that stack is starting from high address and grows to low address of memory. So esp + 8 means a lower address. Also 0x4 and 0x3 is copied in same way. 0x4 into esp+0x4 and 0x3 into esp (That means to the top of the stack). Did you notice some special thing? We push three arguments in reverse order. (5 , 4 and finally 3).

Why that happens? . When we talk about the Stack there is a special concept called LIFO. It is an acronym for Last In First Out. Yes the name says it's all. If we put something on to the top of the stack first we can remove that lastly. Also the last item we put on the stack is the first thing get removed. Actually the word to put something in to the top of stack is PUSH. When we remove an item from the top of stack we call it POP. I think you can understand this in GDB by looking at following image.

copied3Values

Also hear is a graphical image to clear-up any confusion about this situation.

stackaftercopied3values

The next instruction in Main is call testFunction. Call instruction does two things. First it push the return address to the top of the stack. Don't bother too much about ret address. I'll explain about it later. Next it'll overwrite eip with the address of first instruction in testFunction. So testFunction can continue the execution. In the following image you can see the return address is copied to the top of stack. (In a red box)

copiedeip

After we copy the ret-address to stack it will grow to the direction of low memory. So esp now pointing to a different location. (Actually old_esp - 0x4) . Take a look at what inside the red box. Yes we call it return address. There is a memory address in hex form. (0x08048402). Did you notice any special thing about it?. Go to the image we disassembled main and testFunction. You can see 0x08048402 is the address of printf instruction that print the string "after testFunction I'll be printed" . So can you build a connection between them? . Think in this way. After completing the function cpu should jump to main function again. We call it returning to main. Actually CPU should return to next instruction in main after testFunction. During the testFunction EIP will get changed. So if we want to set EIP to a address in main we must save that address in somewhere for future usage. Yes buddy, that place is the stack. That's why we saved that address[return address] in stack. :-)

copiedEipGr

Next there is an instruction called push ebp. EBP stands for extended base pointer. We can use ebp as a offset for fetch arguments. If we copy current value of esp to ebp, both esp and ebp will point to top of stack. Now we can access arguments like ebp+0x4 , ebp+0x8 etc. Wait why we can't use ESP for this purpose? During a function esp is not static. When we push something into stack esp is reduced and if we pop of stack esp will go high. First of all we want to put current value of ebp on the stack.

copiedebp

But why we put ebp in stack? .At the moment ebp is using by main function to fetch arguments. After completing testFunction , main function needs ebp again. So if we backup current value of ebp in stack , after completing testFunction we can restore old value of ebp.

Hear I added a graphical image also.

copiedEbpGr

In above paragraph I said we want to copy esp into ebp.

ebpandespbefore

You can see values of esp and ebp before copy esp to ebp. Next instruction is mov ebp , esp.

ebpandespafter

Hear is the result of above instruction Now both of esp and ebp point to top of the stack.

espToEbpGr

After hat there is a simple assembly instruction called sub esp,0x2c. It'll reduce esp by some value . So stack will grow to lower addresses. Actually what happened hear is we set some space for character buffer. In our C program we allocated bytes for this variable.

char buff[40];
subfromEsp subEspGr

Next five instructions are actually produced to do a special operation. Let's see what they do .

mov eax, DWORD PTR [ebp+0xc]
mov eax, DWORD PTR [ebp+0x8]
First instruction will copy what inside ebp+0xc to eax. In following image you can see that. copyintoeax

Next it will put 0x3 into edx. Yes 0x3 is coming from ebp+0x8. I think now it is clear how we can use ebp as a offset to access arguments.

In above two commands we saw something like DWORD PTR [ebp+0x8]. What that means? When we move something into stack we don't copy actual values. We only put the address of the value into that place. Take this example

if we want to push the value of eax to stack we use push eax. But what if we use push [eax] ? . Then first CPU take the address in eax. After it will check what inside that address and what found on that address is pushed to the stack. I'll explain more about these addressing modes in a separate tutorial.

copytoedx

Now eax contains 4 and edx contains 3. Next there is a lea instruction. It is Load Effective Address. It will load the sum of eax and edx to eax Finally what happened is CPU will calculate 3+4 and put it in eax . Let's see what in eax after above instruction.

leatoeax

Yes it is 7.

ebpOffset

After that we fetch our last argument (5) and add it to our last result(7). So finall value will be 12 (0xc).

returnSum

Now if we think about above 5 instructions , what they did?

int d = a + b + c ;
:-) . Yes we wanted many assembly code lines to do that simple operation. But in C it took one line. Now the job of testFunction is over. Now execution should transferred to main function. Before that let's take a look at the current view of the stack.

stackBeforeRet

In green box we can see three arguments we pushed to stack. After that in a red box we can see the ret-address . Then in the blue box ebp is there . Finally there is a yellow box that filled with 0x000000C (this is the value that we calculated).

Next there is an instruction called leave. It'll move the old value to ebp Did you remember we backed up the old value of ebp in stack. There is another thing to notice. After leave instruction top of the stack contains return address.

leaveInstruction

Finally there is a instruction called ret. It does nothing but take the value from the top of stack and put it in eip. So CPU can execute next things in main function.

retInstruction

OK . A long tutorial. I think I explained all things clearly. If you have any question , please leave a comment. So we can discuss more.

Thank you for reading.

Hi, I'm Thilan. An engineering student from SriLanka. I love to code with Python, JavaScript PHP and C.

Also read

Apr 12
C programming variables explained

In computer science you may heard about virtual memory model. In this model computer memory is....

May 16
Stack architecture theory tutorial

The stack is an important concept in computer science. If you are planning to learn reverse....

Sep 09
SQL Injection Explained

Hello all, I hope you know how to do a SQL injection and have used it .In this tutorial we are....

Comments