这在很大程度上取决于您的体系结构和编译器标志,因此不可能在这里指向单一事物并说“这一定是它”。不过,我可以给你一些建议,你可能会觉得有帮助。
首先,考虑堆栈边界。您可能听说过-mpreferred-stack-boundary=X
GCC 的标志。如果不是,它基本上告诉你的编译器更喜欢你在堆栈上的值是每个 2 X字节。然后,您的编译器将尝试优化您的程序,以使这些值尽可能适合堆栈。另一方面,诸如 GCC 修饰符__packed__
将使编译器尝试将数据尽可能紧密地放入堆栈中。
还有堆栈保护器。基本上,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
换句话说,只有局部变量在比基指针更低的内存中。老实说,可能还有一些其他因素会影响堆栈上局部变量的大小,我忘记包括在内,但我希望这对您有所帮助。继续修改你的程序,你会弄明白的。:)