use*_*694 5 c memory-management buffer-overflow
阅读缓冲区溢出,我遇到了下面给出的示例代码: -
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
Run Code Online (Sandbox Code Playgroud)
这是来自着名的粉碎堆栈的乐趣和利润文章我猜.(参考:http://insecure.org/stf/smashstack.html)
文章说,要为buffer1和buffer2分配空间,需要20个字节(缓冲区1为8个字节,缓冲区2为12个字节),因为只能以字大小的倍数访问存储器地址(在这种情况下,1个字= 4个字节).
但我记得内存是字节可寻址的,即我可以从内存中一次访问1个字节.我将此与处理器的位数相关联.例如,32位处理器可以访问2 ^ 32个存储器位置,并且由于1个存储器位置保持1个字节(8位),因此32位处理器的总可寻址存储器等于(2 ^ 32)/(1024*1024*1024)= 4096 MB = 4GB.
因为在上面的例子中,buffer1和buffer2都是char类型,假设需要1个字节,为什么我们不能分别为buffer1和buffer2分配5个字节和10个字节?
为什么内存访问限制为字大小的倍数?
首先 -内存访问不限于字大小。
正如您所指出的,您可以自由地以各自 CPU 支持的最细粒度访问内存 - 在大多数情况下,这将是字节。
然而,对于 C 中的局部变量,对齐规则有点具体。整个字访问限制与函数局部变量放置在所谓的堆栈
上的事实有关。
CPU 提供堆栈来临时存储和检索内存中的寄存器值,每个程序都有自己的内存用作堆栈空间。这是常见的/在某些情况下需要以与 CPU 使用的寄存器大小完全相同的方式访问堆栈,这样您就不会意外地破坏 CPU 使用的推送/弹出访问。在 32 位系统上,访问大小为每个寄存器 4 字节,对于 64 位系统,访问大小为 8 字节。
因此,在您的示例中,函数的堆栈在 Intel CPU 上可能看起来像这样(取决于操作系统):
|--- function's stack bottom ---|
| 4 byte Code-Segment index |
| 4 byte return address |
| 4 byte buffer1[0..3] |
| 1 byte buffer1[4], 3 byte pad |
| 4 byte buffer2[0..3] |
| 4 byte buffer2[4..7] |
| 2 byte buffer2[8..9], 2 byte pad |
|--- function's stack top ---|
Run Code Online (Sandbox Code Playgroud)
需要填充字节,以便当您的程序运行并从函数内部使用堆栈时,它仍然会正确对齐(相信我,它会经常使用它;))。
例如,push/pop 仍然会产生 4 字节对齐的地址。
请记住:此对齐规则仅适用于堆栈空间 - 全局或静态变量可以位于奇数内存位置(不太可能,但可能)
我希望这不是针对技术人员/低级人员。
[编辑]
如果您考虑以下事情,这里与缓冲区溢出的关系就会变得清晰:如果您知道内存和堆栈布局,您可以操纵诸如返回地址之类的东西。正如您所看到的,通过缓冲区的上溢/下溢,您可以轻松更改堆栈上位于缓冲区上方/下方的值。
在大多数情况下,这会使您的代码崩溃,但如果正确执行,您还可以将一些可执行代码放在堆栈/内存中的某个位置,并更改函数的返回地址以跳转到该代码,而不是返回到调用它的位置。