【问题标题】:Explain esp-ebp in this program在这个程序中解释 esp-ebp
【发布时间】:2013-11-13 05:04:54
【问题描述】:

我有这个 C 函数:

void hello(char * src) {
  char buffer[64];
  strcpy(buffer, src);
  printf("Hello %s !\n", buffer);
}

(其中包含我知道的安全问题)

它的 x86 程序集是

push   ebp
mov    ebp,esp
sub    esp,0x58

为什么是 0x58(即 88)?我希望 64 + 4 + 4 + 4(局部变量缓冲区 + 参数 + 旧 ebp + 返回地址)或其他东西,我错过了什么?

【问题讨论】:

  • 如果是32bits asm,那么一个指针代表4个字节。否则,如果是 64bits asm,则指针代表 8 个字节。
  • 肯定不止是缓冲区。如今,堆栈金丝雀非常普遍。它可以分配一些额外的空间来溢出应该保留的寄存器,例如 esi 和 edi。许多编译器试图保持堆栈对齐到 8。可以在调试版本中添加保护字节以检测缓冲区溢出。更明智的做法是实际命名您使用的编译器。
  • 我建议使用gcc godbolt 尝试不同的数组大小,看看反汇编的变化。
  • @PierreEmmanuelLallemant 它必须是 32 位 x86,因为 OP 使用 push ebp; mov ebp,esp,这在 64 位模式下是不可能的

标签: c assembly x86 cpu-registers


【解决方案1】:

这在很大程度上取决于您的体系结构和编译器标志,因此不可能在这里指向单一事物并说“这一定是它”。不过,我可以给你一些建议,你可能会觉得有帮助。

首先,考虑堆栈边界。您可能听说过 GCC 的 -mpreferred-stack-boundary=X 标志。如果不是,它基本上告诉你的编译器更喜欢你在堆栈上的值是 2X 个字节。然后,您的编译器将尝试优化您的程序,以使这些值尽可能适合堆栈。另一方面,__packed__ 之类的 GCC 修饰符会使编译器尝试将数据尽可能紧密地放入堆栈中。

还有堆栈保护器。基本上,GCC 会在堆栈上放置虚拟值,以确保缓冲区溢出除了对您的程序造成段错误之外不会造成任何伤害(这并不有趣,但比攻击者控制指令指针更好)。您可以轻松地尝试一下:使用任何最新版本的 GCC 并让用户溢出缓冲区。您会注意到程序退出时会显示一条消息,即“检测到堆栈粉碎,已终止”。尝试用-fno-stack-protector编译你的程序,堆栈上分配的本地内存可能会更小。

最后,关于 cdecl 调用约定如何工作的一些小细节,您弄错了。参数在调用函数之前被压入堆栈,这意味着它们在堆栈中的内存更高(请记住,堆栈在内存中向下增长)。下面是一个极其简化的函数示例,它需要 3 个参数并分配 2 个局部整数变量:

# First we push three arguments on the stack in reverse order as they 
# appear in C. The values don't matter here.
pushl $0xc
pushl $0xb
pushl $0xa

# A CALL instruction comes in here to get in the function. The return 
# address is placed on the stack.

# Assume we are in the function now. This function first saves the base 
# pointer, then sets the base pointer to the address in the stack pointer.
pushl %ebp
movl %esp, %ebp

# Now we can allocate our local variables. We need 8 bytes of space for 
# those 2 integer variables (note that this is an extremely simplified 
# example that doesn't consider what I just told you above).
subl $0x8, %esp
# Let's just put 1 and 2 in those variables.
movl $0x1, -4(%ebp)
movl $0x2, -8(%ebp)

# We're done. Put a return value in EAX, then restore the stack- and 
# base pointers.
movl $0x0, %eax
movl %ebp, %esp
popl %ebp
ret

所以基本上,我们的堆栈看起来有点像这样:

16(%ebp)     -> Argument 3
12(%ebp)     -> Argument 2
8(%ebp)      -> Argument 1
4(%ebp)      -> Return address
%ebp         -> Old %ebp pushed on the stack by function
-4(%ebp)     -> Local variable 1
-8(%ebp)     -> Local variable 2

换句话说,只有局部变量位于比基指针低的内存中。老实说,可能还有一些其他因素会影响堆栈上局部变量的大小,我忘记包括在内,但我希望这对您有所帮助。继续修改你的程序,你会弄明白的。 :)

【讨论】:

    【解决方案2】:

    编译器保留更多空间的原因有很多。除了其他人所说的,如果你在 MSVC 上,也许这就是 edit and continue 功能。如果没有编译器名称和编译选项,我无法告诉您更多信息

    【讨论】:

      【解决方案3】:

      好的,这是一个疯狂的猜测,但让我们把它跑到旗杆上,看看会发生什么。

      也许编译器没有针对空间进行优化,这是一个字对齐调整,以节省移位字,以便跨四字边界加载寄存器。

      查看值,0x58 和 8bytes -> 下一个四字边界 96 0x60。从内存中最不重要的四字线中弹出 ebp 更容易(或者它再次是字节序?;) )显着的四字线;前瞻性思维等等。

      编辑:完全正确! (他说的……)

      【讨论】:

        猜你喜欢
        • 2011-08-23
        • 2017-04-24
        • 2011-07-25
        • 1970-01-01
        • 1970-01-01
        • 2014-05-15
        • 2012-01-11
        相关资源
        最近更新 更多