I just got started with buffer overflows and when I look for tutorials everyone has printf#plt and gets#plt in their assembler code, but I don't see them. Am I doing something wrong?
Source code:
#include <stdio.h>
#include <string.h>
int main()
{
char password[16];
int passcheck = 0;
void secret();
printf("\nWhat's the password?\n");
gets(password);
if (strcmp(password, "password1"))
{
printf("\nYou fail/n");
}
else
{
printf("\nCorrect password\n");
passcheck = 1;
}
if(passcheck)
{
secret();
}
return 0;
}
void secret()
{
printf("\nYou got it!!!\n");
}
assembler code:
0x00001e50 <+0>: push %ebp
0x00001e51 <+1>: mov %esp,%ebp
0x00001e53 <+3>: push %edi
0x00001e54 <+4>: push %esi
0x00001e55 <+5>: sub $0x40,%esp
0x00001e58 <+8>: call 0x1e5d <main+13>
0x00001e5d <+13>: pop %eax
0x00001e5e <+14>: lea 0x101(%eax),%ecx
0x00001e64 <+20>: movl $0x0,-0xc(%ebp)
0x00001e6b <+27>: movl $0x0,-0x20(%ebp)
0x00001e72 <+34>: mov %ecx,(%esp)
0x00001e75 <+37>: mov %eax,-0x24(%ebp)
0x00001e78 <+40>: call 0x1f28
0x00001e7d <+45>: lea -0x1c(%ebp),%ecx
0x00001e80 <+48>: mov %ecx,(%esp)
0x00001e83 <+51>: mov %eax,-0x28(%ebp)
0x00001e86 <+54>: call 0x1f22
0x00001e8b <+59>: lea -0x1c(%ebp),%ecx
0x00001e8e <+62>: mov -0x24(%ebp),%edx
0x00001e91 <+65>: lea 0x118(%edx),%esi
0x00001e97 <+71>: mov %esp,%edi
0x00001e99 <+73>: mov %esi,0x4(%edi)
0x00001e9c <+76>: mov %ecx,(%edi)
0x00001e9e <+78>: mov %eax,-0x2c(%ebp)
0x00001ea1 <+81>: call 0x1f2e
0x00001ea6 <+86>: cmp $0x0,%eax
0x00001ea9 <+89>: je 0x1ec8 <main+120>
0x00001eaf <+95>: mov -0x24(%ebp),%eax
0x00001eb2 <+98>: lea 0x122(%eax),%ecx
0x00001eb8 <+104>: mov %ecx,(%esp)
0x00001ebb <+107>: call 0x1f28
0x00001ec0 <+112>: mov %eax,-0x30(%ebp)
0x00001ec3 <+115>: jmp 0x1ee3 <main+147>
0x00001ec8 <+120>: mov -0x24(%ebp),%eax
0x00001ecb <+123>: lea 0x12e(%eax),%ecx
0x00001ed1 <+129>: mov %ecx,(%esp)
0x00001ed4 <+132>: call 0x1f28
0x00001ed9 <+137>: movl $0x1,-0x20(%ebp)
0x00001ee0 <+144>: mov %eax,-0x34(%ebp)
0x00001ee3 <+147>: cmpl $0x0,-0x20(%ebp)
0x00001ee7 <+151>: je 0x1ef2 <main+162>
0x00001eed <+157>: call 0x1f00 <secret>
0x00001ef2 <+162>: xor %eax,%eax
0x00001ef4 <+164>: add $0x40,%esp
0x00001ef7 <+167>: pop %esi
0x00001ef8 <+168>: pop %edi
0x00001ef9 <+169>: pop %ebp
0x00001efa <+170>: ret
0x00001efb <+171>: nopl 0x0(%eax,%eax,1)
Add debug symbols to your binaries by compiling your C program with appropriate switch for your C compiler. For example if you use gcc, use -g switch as is described here:. After that you will be able to see original C symbols names when executing your binary under gdb
Regarding your comment - maybe your object files weren't recompiled from scratch. Try to make clean if you use makefiles or just delete all the object (.o) files and then recompile your program with -ggdb switch (it is the same as -g switch but generates debug info specifically for gdb). After recompiling look in your binary for debug infor - couple of strings like 'printf#plt' and 'gets#plt'.
Related
#include<stdio.h>
#include<string.h>
int main(int argc, char ** argv)
{
char buffer[500];
strcpy(buffer, argv[1]);
return 0;
}
I can compiling this program using gcc -m32 -fno-stack-protector -z execstack -fno-pie -no-pie -g -o vuln vuln.c
On disassembling the main function using the debugger, I am getting this as the output:
Dump of assembler code for function main:
0x0804840b <+0>: lea 0x4(%esp),%ecx
0x0804840f <+4>: and $0xfffffff0,%esp
0x08048412 <+7>: pushl -0x4(%ecx)
0x08048415 <+10>: push %ebp
0x08048416 <+11>: mov %esp,%ebp
0x08048418 <+13>: push %ecx
0x08048419 <+14>: sub $0x204,%esp
0x0804841f <+20>: mov %ecx,%eax
0x08048421 <+22>: mov 0x4(%eax),%eax
0x08048424 <+25>: add $0x4,%eax
0x08048427 <+28>: mov (%eax),%eax
0x08048429 <+30>: sub $0x8,%esp
0x0804842c <+33>: push %eax
0x0804842d <+34>: lea -0x1fc(%ebp),%eax
0x08048433 <+40>: push %eax
0x08048434 <+41>: call 0x80482e0 <strcpy#plt>
0x08048439 <+46>: add $0x10,%esp
0x0804843c <+49>: mov $0x0,%eax
0x08048441 <+54>: mov -0x4(%ebp),%ecx
0x08048444 <+57>: leave
0x08048445 <+58>: lea -0x4(%ecx),%esp
0x08048448 <+61>: ret
End of assembler dump.
GCC version : 6.5.0
OS : Ubuntu 16.04
GDB version : 7.11.1
The tutorial which I was refering was showed this assembly code :
Dump of assembler code for function main:
0x080483fb <+0>: push %ebp
0x080483fc <+1>: mov %esp,%ebp
0x080483fe <+3>: sub $0x1f4,%esp
0x08048404 <+9>: mov 0xc(%ebp),%eax
0x08048407 <+12>: add $0x4,%eax
0x0804840a <+15>: mov (%eax),%eax
0x0804840c <+17>: push %eax
0x0804840d <+18>: lea -0x1f4(%ebp),%eax
0x08048413 <+24>: push %eax
0x08048414 <+25>: call 0x80482d0 <strcpy#plt>
0x08048419 <+30>: add $0x8,%esp
0x0804841c <+33>: mov $0x0,%eax
0x08048421 <+38>: leave
0x08048422 <+39>: ret
End of assembler dump.
I have the following questions:
How can I get the exact same assembly code dump mentioned in the tutorial?
The difference in the output seems because of ecx register. What does that register do and why is it not part of tutorial's assembly code ?
In main function, I constructed buffer array of size 500 which is 1f4 in hexadecimal, that's why the assembly code of the tutorial is subtracting 1f4 from esp register, but my assembly code is subtracting 204 which is 516 in decimal. I am not able to understand this.
Edit: As noted in the comments, If I add -mpreferred-stack-boundary=2 to the compiler flags, then I get the same assembly code as the tutorial. Why?
I have to override a function's return address using buffer oveflow.
The function itself looks like this(I am passing the name argument):
void vuln(char *name)
{
int n = 20;
char buf[1024];
int f[n];
int i;
for (i=0; i<n; i++) {
f[i] = fib(i);
}
strcpy(buf, name);
...
}
I am disassembling it using gdb which gives me the following
0x080485ae <+0>: push %ebp
0x080485af <+1>: mov %esp,%ebp
0x080485b1 <+3>: push %ebx
0x080485b2 <+4>: sub $0x414,%esp
0x080485b8 <+10>: mov %esp,%eax
0x080485ba <+12>: mov %eax,%ebx
0x080485bc <+14>: movl $0x14,-0x10(%ebp)
0x080485c3 <+21>: mov -0x10(%ebp),%eax
0x080485c6 <+24>: lea -0x1(%eax),%edx
0x080485c9 <+27>: mov %edx,-0x14(%ebp)
0x080485cc <+30>: shl $0x2,%eax
0x080485cf <+33>: lea 0x3(%eax),%edx
0x080485d2 <+36>: mov $0x10,%eax
0x080485d7 <+41>: sub $0x1,%eax
0x080485da <+44>: add %edx,%eax
0x080485dc <+46>: mov $0x10,%ecx
0x080485e1 <+51>: mov $0x0,%edx
0x080485e6 <+56>: div %ecx
0x080485e8 <+58>: imul $0x10,%eax,%eax
0x080485eb <+61>: sub %eax,%esp
0x080485ed <+63>: mov %esp,%eax
0x080485ef <+65>: add $0x3,%eax
0x080485f2 <+68>: shr $0x2,%eax
0x080485f5 <+71>: shl $0x2,%eax
0x080485f8 <+74>: mov %eax,-0x18(%ebp)
0x080485fb <+77>: movl $0x0,-0xc(%ebp)
0x08048602 <+84>: jmp 0x8048621 <vuln+115>
0x08048604 <+86>: sub $0xc,%esp
0x08048607 <+89>: pushl -0xc(%ebp)
0x0804860a <+92>: call 0x8048560 <fib>
0x0804860f <+97>: add $0x10,%esp
0x08048612 <+100>: mov %eax,%ecx
0x08048614 <+102>: mov -0x18(%ebp),%eax
0x08048617 <+105>: mov -0xc(%ebp),%edx
0x0804861a <+108>: mov %ecx,(%eax,%edx,4)
0x0804861d <+111>: addl $0x1,-0xc(%ebp)
0x08048621 <+115>: mov -0xc(%ebp),%eax
0x08048624 <+118>: cmp -0x10(%ebp),%eax
0x08048627 <+121>: jl 0x8048604 <vuln+86>
0x08048629 <+123>: sub $0x8,%esp
0x0804862c <+126>: pushl 0x8(%ebp)
0x0804862f <+129>: lea -0x418(%ebp),%eax
0x08048635 <+135>: push %eax
0x08048636 <+136>: call 0x80483c0 <strcpy#plt>
0x0804863b <+141>: add $0x10,%esp
0x0804863e <+144>: sub $0x8,%esp
0x08048641 <+147>: lea -0x418(%ebp),%eax
0x08048647 <+153>: push %eax
0x08048648 <+154>: push $0x80487b7
0x0804864d <+159>: call 0x80483a0 <printf#plt>
0x08048652 <+164>: add $0x10,%esp
0x08048655 <+167>: movl $0x0,-0xc(%ebp)
0x0804865c <+174>: jmp 0x804867f <vuln+209>
0x0804865e <+176>: mov -0x18(%ebp),%eax
0x08048661 <+179>: mov -0xc(%ebp),%edx
=> 0x08048664 <+182>: mov (%eax,%edx,4),%eax
0x08048667 <+185>: sub $0x4,%esp
0x0804866a <+188>: push %eax
0x0804866b <+189>: pushl -0xc(%ebp)
0x0804866e <+192>: push $0x80487c4
0x08048673 <+197>: call 0x80483a0 <printf#plt>
0x08048678 <+202>: add $0x10,%esp
0x0804867b <+205>: addl $0x1,-0xc(%ebp)
0x0804867f <+209>: cmpl $0x13,-0xc(%ebp)
0x08048683 <+213>: jle 0x804865e <vuln+176>
0x08048685 <+215>: mov %ebx,%esp
0x08048687 <+217>: nop
0x08048688 <+218>: mov -0x4(%ebp),%ebx
0x0804868b <+221>: leave
0x0804868c <+222>: ret
The address of the function which should be called with the return of vuln() is 0x804850b.
How am I supposed to know the amount of fillers until I reach the return address to be overwritten?
I guess the name argument should be in the form "a"*n + "\x0b\x85\x04\x08", where n is some number I am trying to guess. I suppose this should be basic stuff but I am still a beginner so please don't judge me...
How am I supposed to know ...
Your code is:
0x080485ae <+0>: push %ebp
0x080485af <+1>: mov %esp,%ebp
...
0x0804862f <+129>: lea -0x418(%ebp),%eax
0x08048635 <+135>: push %eax
0x08048636 <+136>: call 0x80483c0 <strcpy#plt>
Before you enter the function, the return address is at offset 0(%esp).
After the first push, it's at 4(%esp). Since %esp is next copied to %ebp, it's also at 4(%ebp).
Next you see that the location you start copying into is at -0x418(%ebp).
Conclusion: the delta between &buf[0] and &return_address is 0x418 + 4 == 0x41C.
Alternative solution: fill name input with invalid addresses: 0x01010101, 0x01010102, ... 0x010102FF. Execute the code and observe on which address it crashed.
If my calculations are correct, it would crash when vuln tries to return to "slot" 0x41C / 4 == 0x107, which should contain 0x01010208.
In the program binary, how to determine the instructions related to parameter passing of variable argument function "printf"? For example:
#include <stdio.h>
#include <string.h>
int fun(int a, int b){
return a+b;
}
void main (int argc, char* argv[]){
int a = 0;
int b = 1;
int c = 2;
int d = 3;
printf("a:fun(b,c):d: %d:%d:%d\n", a, fun(b,c), d);
}
is assembled as follows:
(gdb) disas main
Dump of assembler code for function main:
0x080483f1 <+0>: push %ebp
0x080483f2 <+1>: mov %esp,%ebp
0x080483f4 <+3>: and $0xfffffff0,%esp
0x080483f7 <+6>: sub $0x20,%esp
0x080483fa <+9>: movl $0x0,0x10(%esp)
0x08048402 <+17>: movl $0x1,0x14(%esp)
0x0804840a <+25>: movl $0x2,0x18(%esp)
0x08048412 <+33>: movl $0x3,0x1c(%esp)
0x0804841a <+41>: mov 0x18(%esp),%eax
0x0804841e <+45>: mov %eax,0x4(%esp)
0x08048422 <+49>: mov 0x14(%esp),%eax
0x08048426 <+53>: mov %eax,(%esp)
0x08048429 <+56>: call 0x80483e4 <fun>
=> 0x0804842e <+61>: mov $0x8048530,%edx
0x08048433 <+66>: mov 0x1c(%esp),%ecx
0x08048437 <+70>: mov %ecx,0xc(%esp)
0x0804843b <+74>: mov %eax,0x8(%esp)
0x0804843f <+78>: mov 0x10(%esp),%eax
0x08048443 <+82>: mov %eax,0x4(%esp)
=> 0x08048447 <+86>: mov %edx,(%esp)
0x0804844a <+89>: call 0x8048300 <printf#plt>
0x0804844f <+94>: leave
0x08048450 <+95>: ret
Whether the instructions related to parameter passing of variable argument function "print" are instructions between two instructions "0x0804842e <+61>: mov $0x8048530,%edx" and " 0x08048447 <+86>: mov %edx,(%esp)".
I have tested many cases of function "printf". In all my tested cases, the instructions related to parameter passing are between these two instructions.
You can find this out by knowing the C calling convention. That is arguments are pushed onto the stack in reverse order.
`0x0804842e <+61>: mov $0x8048530,%edx //Probably the string literal
0x08048433 <+66>: mov 0x1c(%esp),%ecx //Moving 3 literal into %ecx
0x08048437 <+70>: mov %ecx,0xc(%esp) // moving 3 onto top of the arguments on the stack (%esp is the stack pointer)
0x0804843b <+74>: mov %eax,0x8(%esp) //Moving return value from fun onto next slot in the stack, %eax store the return value from a function.
0x0804843f <+78>: mov 0x10(%esp),%eax //Moving 0 literal into %eax
0x08048443 <+82>: mov %eax,0x4(%esp) //Moving %eax into next slot in the stack
0x08048447 <+86>: mov %edx,(%esp) //moving string literal onto the stack
0x0804844a <+89>: call 0x8048300 `//calling printf
I'm working on the irq_lock() / irq_unlock() implementation for an RTOS and found an issue. We want to absolutely minimize how much time the CPU spends with interrupts locked. Right now, our irq_lock() inline function for x86 uses "memory" clobber:
static ALWAYS_INLINE unsigned int _do_irq_lock(void)
{
unsigned int key;
__asm__ volatile (
"pushfl;\n\t"
"cli;\n\t"
"popl %0;\n\t"
: "=g" (key)
:
: "memory"
);
return key;
}
The problem is that the compiler will still reorder potentially expensive operations into the critical section if they just touch registers and not memory. A specific example is here in our kernel's sleep function:
void k_sleep(int32_t duration)
{
__ASSERT(!_is_in_isr(), "");
__ASSERT(duration != K_FOREVER, "");
K_DEBUG("thread %p for %d ns\n", _current, duration);
/* wait of 0 ns is treated as a 'yield' */
if (duration == 0) {
k_yield();
return;
}
int32_t ticks = _TICK_ALIGN + _ms_to_ticks(duration);
int key = irq_lock();
_remove_thread_from_ready_q(_current);
_add_thread_timeout(_current, NULL, ticks);
_Swap(key);
}
The 'ticks' calculation, which does expensive math, gets reordered inside where we lock interrupts, so we are calling __divdi3 with interrupts locked which is not what we want:
Dump of assembler code for function k_sleep:
0x0010197a <+0>: push %ebp
0x0010197b <+1>: mov %esp,%ebp
0x0010197d <+3>: push %edi
0x0010197e <+4>: push %esi
0x0010197f <+5>: push %ebx
0x00101980 <+6>: mov 0x8(%ebp),%edi
0x00101983 <+9>: test %edi,%edi
0x00101985 <+11>: jne 0x101993 <k_sleep+25>
0x00101987 <+13>: lea -0xc(%ebp),%esp
0x0010198a <+16>: pop %ebx
0x0010198b <+17>: pop %esi
0x0010198c <+18>: pop %edi
0x0010198d <+19>: pop %ebp
0x0010198e <+20>: jmp 0x101944 <k_yield>
0x00101993 <+25>: pushf
0x00101994 <+26>: cli
0x00101995 <+27>: pop %esi
0x00101996 <+28>: pushl 0x104608
0x0010199c <+34>: call 0x101726 <_remove_thread_from_ready_q>
0x001019a1 <+39>: mov $0x64,%eax
0x001019a6 <+44>: imul %edi
0x001019a8 <+46>: mov 0x104608,%ebx
0x001019ae <+52>: add $0x3e7,%eax
0x001019b3 <+57>: adc $0x0,%edx
0x001019b6 <+60>: mov %ebx,0x20(%ebx)
0x001019b9 <+63>: movl $0x0,(%esp)
0x001019c0 <+70>: push $0x3e8
0x001019c5 <+75>: push %edx
0x001019c6 <+76>: push %eax
0x001019c7 <+77>: call 0x1000a0 <__divdi3>
0x001019cc <+82>: add $0x10,%esp
0x001019cf <+85>: inc %eax
0x001019d0 <+86>: mov %eax,0x28(%ebx)
0x001019d3 <+89>: movl $0x0,0x24(%ebx)
0x001019da <+96>: lea 0x18(%ebx),%edx
0x001019dd <+99>: mov $0x10460c,%eax
0x001019e2 <+104>: add $0x28,%ebx
0x001019e5 <+107>: mov $0x10162b,%ecx
0x001019ea <+112>: push %ebx
0x001019eb <+113>: call 0x101667 <sys_dlist_insert_at>
0x001019f0 <+118>: mov %esi,0x8(%ebp)
0x001019f3 <+121>: pop %eax
0x001019f4 <+122>: lea -0xc(%ebp),%esp
0x001019f7 <+125>: pop %ebx
0x001019f8 <+126>: pop %esi
0x001019f9 <+127>: pop %edi
0x001019fa <+128>: pop %ebp
0x001019fb <+129>: jmp 0x100f77 <_Swap>
End of assembler dump.
We discovered that we can get the ordering we want by declaring 'ticks' volatile:
Dump of assembler code for function k_sleep:
0x0010197a <+0>: push %ebp
0x0010197b <+1>: mov %esp,%ebp
0x0010197d <+3>: push %ebx
0x0010197e <+4>: push %edx
0x0010197f <+5>: mov 0x8(%ebp),%edx
0x00101982 <+8>: test %edx,%edx
0x00101984 <+10>: jne 0x10198d <k_sleep+19>
0x00101986 <+12>: call 0x101944 <k_yield>
0x0010198b <+17>: jmp 0x1019f5 <k_sleep+123>
0x0010198d <+19>: mov $0x64,%eax
0x00101992 <+24>: push $0x0
0x00101994 <+26>: imul %edx
0x00101996 <+28>: add $0x3e7,%eax
0x0010199b <+33>: push $0x3e8
0x001019a0 <+38>: adc $0x0,%edx
0x001019a3 <+41>: push %edx
0x001019a4 <+42>: push %eax
0x001019a5 <+43>: call 0x1000a0 <__divdi3>
0x001019aa <+48>: add $0x10,%esp
0x001019ad <+51>: inc %eax
0x001019ae <+52>: mov %eax,-0x8(%ebp)
0x001019b1 <+55>: pushf
0x001019b2 <+56>: cli
0x001019b3 <+57>: pop %ebx
0x001019b4 <+58>: pushl 0x1045e8
0x001019ba <+64>: call 0x101726 <_remove_thread_from_ready_q>
0x001019bf <+69>: mov 0x1045e8,%eax
0x001019c4 <+74>: mov -0x8(%ebp),%edx
0x001019c7 <+77>: movl $0x0,0x24(%eax)
0x001019ce <+84>: mov %edx,0x28(%eax)
0x001019d1 <+87>: mov %eax,0x20(%eax)
0x001019d4 <+90>: lea 0x18(%eax),%edx
0x001019d7 <+93>: add $0x28,%eax
0x001019da <+96>: mov %eax,(%esp)
0x001019dd <+99>: mov $0x10162b,%ecx
0x001019e2 <+104>: mov $0x1045ec,%eax
0x001019e7 <+109>: call 0x101667 <sys_dlist_insert_at>
0x001019ec <+114>: mov %ebx,(%esp)
0x001019ef <+117>: call 0x100f77 <_Swap>
0x001019f4 <+122>: pop %eax
0x001019f5 <+123>: mov -0x4(%ebp),%ebx
0x001019f8 <+126>: leave
0x001019f9 <+127>: ret
End of assembler dump.
However this just fixes it in one spot. We really need a way to modify the irq_lock() implementation such that it does the right thing everywhere, and right now the "memory" clobber is not sufficient.
Since your architecture is x86 anyway, try using __sync_synchronize instead of the memory clobber. It's a full hardware memory barrier supported by x86.
I have been trying to skip an instruction by changing the return address through stack smashing. The following code skips a++ in main and prints an output of "1 3". I have executed this code on a 32-bit intel machine.
#include<stdio.h>
void fun(int a,int b) {
// buffer
char buf[8];
char *p;
p = (char *)buf+24;
*p=*p+5;
return;
}
int main() {
int a=1,b=2;
fun(a,b);
a++;
b++;
printf("%d %d",a,b);
}
I am unable to understand why return address is stored at a displacement of 24 bytes from starting address of buf. I have tried executing the same code on a different 32-bit intel machine and I had to use a displacement of 20 bytes instead of 24 bytes. I have put my understanding in the following figure. I am not sure about what fills the gap represented by "?" in the figure. Does gcc put any canary value there or am I missing something ?
Link to figure: http://www.cse.iitb.ac.in/~shashankr/stack.png
Smashing the stack example3.c confusion asked the same question but could not explain the reason for displacement in general.
The following figure gives a view of the stack obtained by placing a breakpoint in function.
(source: shashankr at www.cse.iitb.ac.in)
The following is the assembly code for main and fun:
Dump of assembler (fun):
0x08048434 <+0>: push %ebp
0x08048435 <+1>: mov %esp,%ebp
0x08048437 <+3>: sub $0x18,%esp
0x0804843a <+6>: mov %gs:0x14,%eax
0x08048440 <+12>: mov %eax,-0xc(%ebp)
0x08048443 <+15>: xor %eax,%eax
0x08048445 <+17>: lea -0x14(%ebp),%eax
0x08048448 <+20>: add $0x18,%eax
0x0804844b <+23>: mov %eax,-0x18(%ebp)
0x0804844e <+26>: mov -0x18(%ebp),%eax
0x08048451 <+29>: movzbl (%eax),%eax
0x08048454 <+32>: add $0x5,%eax
0x08048457 <+35>: mov %eax,%edx
0x08048459 <+37>: mov -0x18(%ebp),%eax
0x0804845c <+40>: mov %dl,(%eax)
0x0804845e <+42>: mov -0xc(%ebp),%eax
0x08048461 <+45>: xor %gs:0x14,%eax
0x08048468 <+52>: je 0x804846f <fun+59>
0x0804846a <+54>: call 0x8048350 <__stack_chk_fail#plt>
0x0804846f <+59>: leave
0x08048470 <+60>: ret
Dump of assembler (main)
0x08048471 <+0>: push %ebp
0x08048472 <+1>: mov %esp,%ebp
0x08048474 <+3>: and $0xfffffff0,%esp
0x08048477 <+6>: sub $0x20,%esp
0x0804847a <+9>: movl $0x1,0x18(%esp)
0x08048482 <+17>: movl $0x2,0x1c(%esp)
0x0804848a <+25>: mov 0x1c(%esp),%eax
0x0804848e <+29>: mov %eax,0x4(%esp)
0x08048492 <+33>: mov 0x18(%esp),%eax
0x08048496 <+37>: mov %eax,(%esp)
0x08048499 <+40>: call 0x8048434 <fun>
0x0804849e <+45>: addl $0x1,0x18(%esp)
0x080484a3 <+50>: addl $0x1,0x1c(%esp)
0x080484a8 <+55>: mov $0x80485a0,%eax
0x080484ad <+60>: mov 0x1c(%esp),%edx
0x080484b1 <+64>: mov %edx,0x8(%esp)
0x080484b5 <+68>: mov 0x18(%esp),%edx
0x080484b9 <+72>: mov %edx,0x4(%esp)
0x080484bd <+76>: mov %eax,(%esp)
0x080484c0 <+79>: call 0x8048340 <printf#plt>
0x080484c5 <+84>: leave
0x080484c6 <+85>: ret
I believe the answer is nothing. Are you having different gcc versions? Anyway a compiler is allowed to allocate a bit more stack than necessary. Perhaps it's the initial "guess" based on the number of variables, but which isn't reduced by optimization stages, which are allowed to move any variable to a register. Or it's some reservoir to save ecx,ebp or other registers in case the subroutine needs to.
There's anyway one fixed address variable to overcome the problem: a.
Return address = &a[-1].