I am trying to write a buffer overflow exercise in c for students.
Normally the stack frame consists of the function parameters, the return address, the base pointer and local variables. But I detected, that sometimes additional registers are saved along with the base pointer. I remember from class that calee saved registers have to be saved before they are used. But there are cases where the compilation of C code produces assembly, which saves and uses registers apperently without purpose. Please explain this behaviour to me.
Assume the main function
int main (int argc, char** argv) {
func();
return 0;
}
and the function
void func() {
char buf[5];
strcpy(buf,"AAAA");
strcpy(buf,"BBBB");
}
If I debug the resulting executable with gdb
break func
run
info frame
everything is fine and the stack frame only contains ebp and eip.
If I use
void func() {
char buf[5];
gets(buf);
}
I get
Saved registers:
ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4
So ebx is additionally saved in the stack frame? Why? If I run
disas func
I get
Dump of assembler code for function func:
0x56555730 <+0>: push %ebp
0x56555731 <+1>: mov %esp,%ebp
0x56555733 <+3>: push %ebx
0x56555734 <+4>: sub $0x8,%esp
0x56555737 <+7>: call 0x5655576e <__x86.get_pc_thunk.ax>
0x5655573c <+12>: add $0x18c4,%eax
=> 0x56555741 <+17>: lea -0x9(%ebp),%edx
0x56555744 <+20>: push %edx
0x56555745 <+21>: mov %eax,%ebx
0x56555747 <+23>: call 0x56555590 <gets@plt>
0x5655574c <+28>: add $0x4,%esp
0x5655574f <+31>: nop
0x56555750 <+32>: mov -0x4(%ebp),%ebx
0x56555753 <+35>: leave
0x56555754 <+36>: ret
End of assembler dump.
So ebx is saved. Ok. But what is it used for? eax is moved in ebx before calling gets(). But it is not used afterwards. The old ebx is just restored from the stack before leaving and returning. It seems useless.
Btw. Whats the whole call get_pc_thunk
stuff?
Comparable behaviour, if I use printf instead of gets:
void func() {
char buf[5];
strcpy(buf, "AAAA");
printf("%s",buf);
}
gdb output:
(gdb) info frame
Stack level 0, frame at 0xffffd1d8:
eip = 0x56555741 in func (/home/mischa/stuff/test/test.c:35); saved eip = 0x56555779
called by frame at 0xffffd1e0
source language c.
Arglist at 0xffffd1d0, args:
Locals at 0xffffd1d0, Previous frame's sp is 0xffffd1d8
Saved registers:
ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4
(gdb) disas func
Dump of assembler code for function func:
0x56555730 <+0>: push %ebp
0x56555731 <+1>: mov %esp,%ebp
0x56555733 <+3>: push %ebx
0x56555734 <+4>: sub $0x8,%esp
0x56555737 <+7>: call 0x56555780 <__x86.get_pc_thunk.ax>
0x5655573c <+12>: add $0x18c4,%eax
=> 0x56555741 <+17>: movl $0x41414141,-0x9(%ebp)
0x56555748 <+24>: movb $0x0,-0x5(%ebp)
0x5655574c <+28>: lea -0x9(%ebp),%edx
0x5655574f <+31>: push %edx
0x56555750 <+32>: lea -0x17f0(%eax),%edx
0x56555756 <+38>: push %edx
0x56555757 <+39>: mov %eax,%ebx
0x56555759 <+41>: call 0x565555a0 <printf@plt>
0x5655575e <+46>: add $0x8,%esp
0x56555761 <+49>: nop
0x56555762 <+50>: mov -0x4(%ebp),%ebx
0x56555765 <+53>: leave
0x56555766 <+54>: ret
End of assembler dump.
Can someone please explain this to me?
I use cmake for compiling with the following CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
# projectname is the same as the main-executable
project(test)
# compile with 32 bit
add_definitions('-m32')
# Disable compiler optimization
add_definitions('-O0')
# include debugging information
add_definitions('-g')
# Align items on the stack to 4 bytes. This makes stuff easier.
# See https://stackoverflow.com/questions/1061818/stack-allocation-padding-and-alignment
add_definitions('-mpreferred-stack-boundary=2')
# disable compiler buffer overflow protection
add_definitions('-z execstack -z norelro -fno-stack-protector')
# executable source code
add_executable(test test.c)
cmake seems to use gcc.
Your compiler toolchain has been configured (probably by your distro) to produce Position-Independent Executables (PIE) by default. On 32-bit x86, in order for position-independent code to call functions that may reside in a different library from the caller, the address of the calling module's GOT must be loaded into ebx
at call time; this is an ABI requirement. Since ebx
is a call-saved register in the x86 ABI, the caller must save and later restore it before returning to its own caller.
This article I wrote on the topic a while back may be informative:
https://ewontfix.com/18/
On very recent versions of gcc, the new -fno-plt
option may avoid this issue by inlining the load from the GOT rather than using the PLT which depends on ebx
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With