The Call Stack - a Practical Review

Posted on Mon 05 March 2012 in reversing

I can't think of a better way to start a new week other than revising the stack. Why would I want to do such a thing? Well, it's always good to know what's going on under the hood, and I'm bored.

Jokes aside, the reason for doing this was that I started "playing" this wargame called IO @ SmashTheStack. Those guys have an amazing setup ready for you to exploit all the way to the root. I'm enjoying the game a lot so far, and when I reached level 5, I realized a good call stack review was in place. We will also review some gdb commands on the go, so let's get to it.

Let's try to avoid being Wikipedia, that's what Wikipedia it's for. Instead, let's do a practice session which will help us revise the important concepts of the call stack.

For reference, use the following articles as indicated:

http://en.wikipedia.org/wiki/Stack_(abstract_data_type)#Hardware_stacks - Revise on pushing and poping. http://en.wikipedia.org/wiki/Call_stack#Structure has the visual image of the stack that we need to keep in mind. Most important are frame and stack pointers.
http://en.wikipedia.org/wiki/Endianness - You need to understand this in order to be able to read bytes, words, etc. Remember all this stuff is machine dependent. For this article we will work with a 32-bit, little-endian CPU architecture, like the x86 family.

And now let's see this in action. Here's some C code:

void draw_line(int c) {
        int line;
        line = 7;
        printf("received %d\n",c);
        return;
}
void draw_circle() {
        int circle;
        circle = 5;
        draw_line(circle);
        circle = 10;
        return;
}
int main(int argc, char **argv) {
        int in_main;
        in_main = 3;
        draw_circle();
        return 8;
}

Main calls draw_circle() which calls draw_line() with a parameter which is printed out. That's it.

Let's compile and fire up gdb to take a look at the disassembled binary.

level5@io:/tmp$ gcc iwanttorevisethestack.c -o iwanttorevisethestack
level5@io:/tmp$ gdb -q iwanttorevisethestack Reading symbols from /tmp/iwanttorevisethestack...(no debugging symbols found)...done. (gdb) set disassembly-flavor intel

draw_line() is called from draw_circle(). Let's see the address indraw_circle() where this happens:

(gdb) disass draw_circle ... 0x080483fa : call 0x80483c4 0x080483ff : mov DWORD PTR [ebp-0xc],0xa ...

So 0x080483ff, after the CALL, is where the execution must return to once we're done in draw_line(). Let's remember that and place a breakpoint in draw_line(), so we can observe the stack from a fine position.

(gdb) b *draw_line+6 Breakpoint 1 at 0x80483ca (gdb) run Starting program: /tmp/iwanttorevisethestack`

`Breakpoint 1, 0x080483ca in draw_line () (gdb) x/6i $pc ;; print the next 6 instructions 0x80483ca : mov DWORD PTR [ebp-0xc],0x7 ;; line = 7; 0x80483d1 : mov eax,0x80484f0 0x80483d6 : mov edx,DWORD PTR [ebp+0x8] 0x80483d9 : mov DWORD PTR [esp+0x4],edx 0x80483dd : mov DWORD PTR [esp],eax 0x80483e0: call 0x80482fc ;; printf("received %d\n",c);

We're in the middle of draw_line(). Remember that $esp always points to the top of the stack, and it changes whenever there's a POP or PUSH. Let's see the registers:

(gdb) p $ebp ;; our Extended Base Pointer or frame pointer $1 = (void *) 0xbfffda78 (gdb) p $esp ;; stack pointer, top address of the stack $2 = (void *) 0xbfffda50
(gdb) p $ebp > $esp $3 = 1
(gdb) p $ebp - $esp $4 = 40

We can see how $ebp > $esp and there's 40 bytes in between them. Let's take a look and see how the stack grows in DECREASING memory address:

(gdb) x/10xw $esp 0xbfffda50: 0x00132a54 0x00000000 0xb7fffbb8 0x00000001 0xbfffda60: 0x00000000 0x00000001 0x001328f8 0x006aeff4 0xbfffda70: 0x00670339 0x0059c3a5

I don't know how to print memory in decreasing order, but it would be cool here for a better visual understanding. Let's just think that if there was a new stack frame, it would be literally on top of theprevious block above, thus growing up.

Let's execute one instruction, DWORD PTR [ebp-0xc],0x7, to see where that value is stored.

(gdb) si ;; step in... 0x080483d1 in draw_line ()
(gdb) x/10xw $esp 0xbfffda50: 0x00132a54 0x00000000 0xb7fffbb8 0x00000001 0xbfffda60: 0x00000000 0x00000001 0x001328f8 0x00000007 0xbfffda70: 0x00670339 0x0059c3a5
(gdb) p $ebp-0xc $5 = (void *) 0xbfffda6c (gdb) x/1u $ebp-0xc 0xbfffda6c: 7

And there we have our 4 byte int "line" variable with a value of 7. Now let's see what $ebp points to.

(gdb) p $ebp $6 = (void *) 0xbfffda78 (gdb) x/1xw $ebp ;; print 1 hex word @ $ebp 0xbfffda78: 0xbfffdaa8 (gdb) x/12xw $ebp 0xbfffda78: 0xbfffdaa8 0x080483ff 0x00000005 0x080495ec 0xbfffda88: 0xbfffda98 0x080482c8 0x00124040 0x080495ec 0xbfffda98: 0xbfffdac8 0x00000005 0x006af304 0x006aeff4
(gdb) x/1xw $ebp+4 0xbfffda7c: 0x080483ff

First, $ebp points at 0xbfffdaa8, which is the end of our stack frame. The next word is 0x080483ff, which is the ADDRESS OF draw_circle+24! Then draw_line() was called like draw_line(5), we can find that 5 here too, 0x00000005.

So far we have found:

  • In $esp "domain", the local variable "line"
  • In $ebp "domain", the argument to draw_line(), the address of draw_circle() and the address of the end of the stack frame

After our stack frame, the stack frame of draw_circle() is present, which is there waiting for us to return. Let's take a look at it from where we are now:

(gdb) x/25xw $ebp 0xbfffda78: 0xbfffdaa8 0x080483ff 0x00000005 0x080495ec 0xbfffda88: 0xbfffda98 0x080482c8 0x00124040 0x080495ec 0xbfffda98: 0xbfffdac8 0x00000005 0x006af304 0x006aeff4 0xbfffdaa8: 0xbfffdac8 0x0804841e 0x0059c5a5 0x00124040 0xbfffdab8: 0x0804844b 0x00000003 0x08048440 0x00000000 0xbfffdac8: 0xbfffdb48 0x00583c76 0x00000001 0xbfffdb74 0xbfffdad8: 0xbfffdb7c

And there we can see the following:

From 0xbfffdaa8 the beginning of the stack frame for draw_circle(), as pointed by our $ebp. In 0xbfffdaa8 we find another address, the end of the stack frame of draw_circle(). And in 0xbfffdaa8 + 4, as before, we find the address of an instruction... 0x0804841e which is...

(gdb) x/2i main+17 0x08048419 : call 0x80483e7 0x0804841e : mov eax,0x8

0x0804841e is the return address where we have to return to when we're done in draw_circle()!
Technically, it's the stored \$eip, which we will POP when RET is executed.

Don't forget to analyze the frame with:

(gdb) info frame
(gdb) info f 1`  
(gdb) quit