I was practicing some reverse engineering crack-mes as part of our university curriculum, and I have a question around the arrangement of variables on the stack.
I have a very basic C++ code, like so:
void foo(int x){
int a=0;
int b=x;
int c=3;
int z=a+b+c;
return;
}
int main(){
foo(5);
return 0;
}
I compile the program, like so:
g++ program.cpp -o program -ggdb
I run gdb with
gdb -q program
And inside gdb
set disassembly-flavor intel
disass foo
I expect the stack frame layout of foo
to have the variable a
on top of b
, followed by c
and then z
. But that does not seem to be the case.
I have stumbled across this answer in which the explanation shows a similar layout of how variables are arranged. I'd expect that x
would be at the highest address in the frame, right below ebp
. But the answer clearly shows z
being the closest to ebp
, given that it is at ebp - 4
but x
is at ebp - 12
, which seems counter intuitive to me.
I have read a bunch of books, and have seen a couple of online videos on buffer overflows where it is said that the first variable the compiler encounters is the first to be placed on the stack; and order of variable declaration can effect what variables are overwritten in case there is a buffer next to them that's overflown. Is this still a valid statement to make?
Why do I see variables arranged in such an order? Kindly help me out, I'm not sure if my facts are outdated or am I missing something fundamental.
PS: I am using Ubuntu 16.04 Desktop, with the latest version of GCC/G++
Edit 1: Adding disassembly of function foo
ubuntu@Ubuntu:~$ gdb -q program
Reading symbols from program...
(gdb) set disassembly intel
(gdb) disass foo
Dump of assembler code for function foo(int):
0x0000000000001125 <+0>: push rbp
0x0000000000001126 <+1>: mov rbp,rsp
0x0000000000001129 <+4>: mov DWORD PTR [rbp-0x14],edi
0x000000000000112c <+7>: mov DWORD PTR [rbp-0x10],0x0
0x0000000000001133 <+14>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000001136 <+17>: mov DWORD PTR [rbp-0xc],eax
0x0000000000001139 <+20>: mov DWORD PTR [rbp-0x8],0x3
0x0000000000001140 <+27>: mov edx,DWORD PTR [rbp-0x10]
0x0000000000001143 <+30>: mov eax,DWORD PTR [rbp-0xc]
0x0000000000001146 <+33>: add edx,eax
0x0000000000001148 <+35>: mov eax,DWORD PTR [rbp-0x8]
0x000000000000114b <+38>: add eax,edx
0x000000000000114d <+40>: mov DWORD PTR [rbp-0x4],eax
0x0000000000001150 <+43>: nop
0x0000000000001151 <+44>: pop rbp
0x0000000000001152 <+45>: ret
End of assembler dump.
(gdb) b *foo+43
Breakpoint 1 at 0x1150: file program.cpp, line 6.
(gdb) r Starting program: /home/ubuntu/program Breakpoint 1, foo (x=5) at program.cpp:6
6 return;
(gdb) p &a
$1 = (int *) 0x7fffffffdfb0
(gdb) p $rbp-0x10
$2 = (void *) 0x7fffffffdfb0
(gdb) p &b
$3 = (int *) 0x7fffffffdfb4
(gdb) p $rbp-0xc
$4 = (void *) 0x7fffffffdfb4
(gdb) p &c
$5 = (int *) 0x7fffffffdfb8
(gdb) p $rbp-0x8
$6 = (void *) 0x7fffffffdfb8
(gdb) p &z
$7 = (int *) 0x7fffffffdfbc
(gdb) p $rbp-0x4
$8 = (void *) 0x7fffffffdfbc
(gdb)