易混淆的点:栈的增长方向 和 缓冲区内的数据写入方向是相反的
核心概念澄清
- 栈的增长方向:指当新的栈帧(函数调用)或新的局部变量被分配时,栈指针(SP)移动的方向。 - 从高地址向低地址增长(这是绝大多数现代系统,如x86/x86-64架构的标准行为)。 
- 这决定了不同变量在栈上的相对位置。 
 
- 缓冲区内的数据写入方向:指当向一个数组或缓冲区(如 - char buffer[10])写入数据时,字节被放置的顺序。- 从低地址向高地址填充(这是内存操作的基本逻辑,与架构无关)。 
- 这决定了溢出时会影响到哪个相邻的变量。 
 
结合例子进行图解
假设我们有如下代码,栈的起始位置在 0x1050:
void function() {int authenticated = 0; // 第9行,先声明char buffer[4];        // 第10行,后声明strcpy(buffer, "OVERFLOW!"); // 不安全拷贝,数据过长
}第一步:栈帧分配与变量布局(栈的增长方向)
当 function 被调用时,系统会为其在栈上分配空间。
- authenticated先被分配。栈指针向低地址移动,假设- authenticated被放在地址- 0x104C-- 0x1048。
- 接着 - buffer被分配。栈指针继续向低地址移动,- buffer被放在地址- 0x1044-- 0x1047。
此时的内存布局(从高到低)如下:
| 内存地址 | 存储内容 | 说明 | 
|---|---|---|
| 0x1050 | ... | 调用者的栈帧 | 
| 0x104C | authenticated(字节0) | 高地址,先入栈的变量 | 
| 0x104B | authenticated(字节1) | |
| 0x104A | authenticated(字节2) | |
| 0x1049 | authenticated(字节3) | |
| 0x1048 | ||
| 0x1047 | buffer[3] | 后入栈的变量 | 
| 0x1046 | buffer[2] | |
| 0x1045 | buffer[1] | |
| 0x1044 | buffer[0] | 低地址,缓冲区的起始位置 | 
关键点一:由于栈向低地址增长,先声明的 authenticated 确实在高地址,后声明的 buffer 在低地址。
第二步:向缓冲区写入数据(缓冲区内的写入方向)
现在执行 strcpy(buffer, "OVERFLOW!")。字符串 "OVERFLOW!" (包括结尾的 \0) 会被复制到 buffer 开始的地址。
- 字符 - 'O'被写入- buffer[0](地址- 0x1044)。
- 字符 - 'V'被写入- buffer[1](地址- 0x1045)。
- 字符 - 'E'被写入- buffer[2](地址- 0x1046)。
- 字符 - 'R'被写入- buffer[3](地址- 0x1047)。
- 此时,4字节的 - buffer已满。但字符串还没完,于是开始溢出...
- 字符 - 'F'被写入下一个相邻的字节,即地址- 0x1048。
- 字符 - 'L'被写入- 0x1049。
- 字符 - 'O'被写入- 0x104A。
- 字符 - 'W'被写入- 0x104B。
- 字符 - '!'被写入- 0x104C。
- 字符 - '\0'被写入- 0x104D。
关键点二:数据写入是从 buffer 的起始低地址 (0x1044) 开始,逐步向高地址填充。当填满 buffer 后,继续写入就会覆盖更高地址的内存。
第三步:溢出后果
从上面的写入过程可以看出:
- 地址 - 0x1048到- 0x104D原本不属于- buffer。
- 地址 - 0x1049到- 0x104C正好是- authenticated变量所在的位置!
- 字符 - 'W'和- '!'覆盖了- authenticated的部分数据,将其从- 0修改为了一个非零值。
总结与类比
可以把栈想象成一本从后往前写的笔记本(栈向低地址增长):
- 你先写第一章( - authenticated),写在笔记本的靠后位置(高地址)。
- 然后你写第二章( - buffer),写在笔记本的靠前位置(低地址)。
但当你具体写第二章的内容时,你依然是从左到右(向高地址)书写的。如果你写得太长,超出了第二章预留的页面,你就会写到第一章的内容上,把第一章的字给擦掉覆盖了。
结论:
- 栈的增长方向(分配变量):高地址 -> 低地址。这决定了 - authenticated在- buffer的“上方”(高地址)。
- 数据的写入方向(填充变量):低地址 -> 高地址。这决定了当 - buffer溢出时,数据会向“上方”(高地址)蔓延,从而覆盖- authenticated。
