为什么ebx保存在一个简单函数的堆栈框架中,调用得到?

Mic*_*alm 6 c compiler-construction assembly gcc reverse-engineering

我正在尝试用c为学生写一个缓冲区溢出练习.

通常,堆栈帧由函数参数,返回地址,基指针和局部变量组成.但我发现,有时候附加的寄存器会与基指针一起保存.我从课堂上记得,calee保存的寄存器必须在使用之前保存.但有些情况下,C代码的编译会产生汇编,这会毫无目的地保存和使用寄存器.请向我解释这个行为.

假设主要功能

int main (int argc, char** argv) {
    func();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

和功能

void func() {
    char buf[5];
    strcpy(buf,"AAAA");
    strcpy(buf,"BBBB");
}
Run Code Online (Sandbox Code Playgroud)

如果我使用gdb调试生成的可执行文件

break func
run
info frame
Run Code Online (Sandbox Code Playgroud)

一切都很好,堆栈框架只包含ebp和eip.

如果我使用

void func() {
    char buf[5];
    gets(buf);
}
Run Code Online (Sandbox Code Playgroud)

我明白了

 Saved registers:
  ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4
Run Code Online (Sandbox Code Playgroud)

那么ebx是否另外保存在堆栈框架中?为什么?如果我跑

disas func
Run Code Online (Sandbox Code Playgroud)

我明白了

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.
Run Code Online (Sandbox Code Playgroud)

所以ebx得救了.好.但它用于什么?在调用gets()之前,eax在ebx中移动.但之后没有使用它.在离开和返回之前,旧的ebx刚从堆栈中恢复.这似乎毫无用处.顺便说一句.是什么call get_pc_thunk东西?

可比行为,如果我使用printf而不是gets:

void func() {
    char buf[5];
    strcpy(buf, "AAAA");
    printf("%s",buf);
}
Run Code Online (Sandbox Code Playgroud)

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   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.
Run Code Online (Sandbox Code Playgroud)

有人可以向我解释一下吗?

我使用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 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)
Run Code Online (Sandbox Code Playgroud)

cmake似乎使用gcc.

R..*_*R.. 11

您的编译器工具链已经配置(可能由您的发行版),以生成默认情况下位置无关的可执行文件(PIE).在32位x86上,为了使位置无关的代码能够调用可能与调用者位于不同库中的函数,必须ebx在调用时加载调用模块的GOT的地址.这是ABI的要求.由于ebx是x86 ABI中的调用保存寄存器,调用者必须先保存并稍后将其恢复,然后再返回自己的调用者.

我在前一段时间写的关于这个主题的文章可能是有用的:

https://ewontfix.com/18/

在最近的gcc版本中,新-fno-plt选项可以通过内联来自GOT的负载而不是使用依赖的PLT来避免此问题ebx.