我正在尝试用c为学生写一个缓冲区溢出练习.
通常,堆栈帧由函数参数,返回地址,基指针和局部变量组成.但我发现,有时候附加的寄存器会与基指针一起保存.我从课堂上记得,calee保存的寄存器必须在使用之前保存.但有些情况下,C代码的编译会产生汇编,这会毫无目的地保存和使用寄存器.请向我解释这个行为.
假设主要功能
int main (int argc, char** argv) { func(); return 0; }
和功能
void func() { char buf[5]; strcpy(buf,"AAAA"); strcpy(buf,"BBBB"); }
如果我使用gdb调试生成的可执行文件
break func run info frame
一切都很好,堆栈框架只包含ebp和eip.
如果我使用
void func() { char buf[5]; gets(buf); }
我明白了
Saved registers: ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4
那么ebx是否另外保存在堆栈框架中?为什么?如果我跑
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 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 0x565555900x5655574c <+28>: add $0x4,%esp 0x5655574f <+31>: nop 0x56555750 <+32>: mov -0x4(%ebp),%ebx 0x56555753 <+35>: leave 0x56555754 <+36>: ret End of assembler dump.
所以ebx得救了.好.但它用于什么?在调用gets()之前,eax在ebx中移动.但之后没有使用它.在离开和返回之前,旧的ebx刚从堆栈中恢复.这似乎毫无用处.顺便说一句.是什么call get_pc_thunk
东西?
可比行为,如果我使用printf而不是gets:
void func() { char buf[5]; strcpy(buf, "AAAA"); printf("%s",buf); }
gdb输出:
(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 0x565555a00x5655575e <+46>: add $0x8,%esp 0x56555761 <+49>: nop 0x56555762 <+50>: mov -0x4(%ebp),%ebx 0x56555765 <+53>: leave 0x56555766 <+54>: ret End of assembler dump.
有人可以向我解释一下吗?
我使用cmake进行编译,使用以下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 /sf/ask/17360801/ 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似乎使用gcc.
您的编译器工具链已经配置(可能由您的发行版),以生成默认情况下位置无关的可执行文件(PIE).在32位x86上,为了使位置无关的代码能够调用可能与调用者位于不同库中的函数,必须ebx
在调用时加载调用模块的GOT的地址.这是ABI的要求.由于ebx
是x86 ABI中的调用保存寄存器,调用者必须先保存并稍后将其恢复,然后再返回自己的调用者.
我在前一段时间写的关于这个主题的文章可能是有用的:
https://ewontfix.com/18/
在最近的gcc版本中,新-fno-plt
选项可以通过内联来自GOT的负载而不是使用依赖的PLT来避免此问题ebx
.