缓冲区溢出 - 局部变量在堆栈上的顺序
Buffer overflow - order of local variables on stack
我对局部变量在堆栈上的排序方式感到很困惑。我知道,(在 Intel x86 上)局部变量在代码中从高地址存储到低地址。很明显,这段代码:
int i = 0;
char buffer[4];
strcpy(buffer, "aaaaaaaaaaaaaaa");
printf("%d", i);
产生这样的东西:
1633771873
i
变量被溢出的缓冲区覆盖。
但是,如果我交换前两行:
char buffer[4];
int i = 0;
strcpy(buffer, "aaaaaaaaaaaaaaa");
printf("%d", i);
输出完全相同。
怎么可能? i
的地址低于 buffer
的地址,因此缓冲区溢出应该会覆盖其他数据,但不会覆盖 i
。还是我遗漏了什么?
局部变量的顺序没有规定,所以编译器通常可以随意分配它们。但另一方面,编译器会使用许多策略来减少您自愿尝试做的事情发生的可能性。
其中一个安全增强措施是分配一个始终远离其他标量变量的缓冲区,因为数组可以越界寻址并且更倾向于膨胀相邻变量。另一个技巧是在数组之后添加一些空陷阱 space 来为边界问题创建一种隔离。
无论如何你可以使用调试器查看程序集以确认变量定位。
如果您想查看编译器如何分配局部变量,请尝试使用 gcc -S
进行编译,这将输出汇编代码。在汇编代码中,您可以看到编译器如何选择对变量进行排序。
在编译器如何选择对局部变量进行排序时要记住的一件事是每个 char 只需要按 1 对齐(这意味着它可以从内存的任何字节开始),另一方面, int必须按 4 对齐(这意味着它只能从可被 4 整除的字节开始),因此根据对齐方式,编译器有自己的逻辑来避免数据为空字节,这意味着它经常将变量组合在一起具有一定顺序的相似类型。所以即使你这样定义它们:
int a;
char c;
int b;
char d;
很可能编译器将内存中的 int 和 char 组合在一起,因此从顶部的低内存到底部的高内存的内存可能类似于:
low memory
| | | char d | char c|
| int b |
| int a |
high memory
每块||代表一个字节,一整行代表4个字节。
找个时间尝试一下汇编代码,这很有趣。
我对局部变量在堆栈上的排序方式感到很困惑。我知道,(在 Intel x86 上)局部变量在代码中从高地址存储到低地址。很明显,这段代码:
int i = 0;
char buffer[4];
strcpy(buffer, "aaaaaaaaaaaaaaa");
printf("%d", i);
产生这样的东西:
1633771873
i
变量被溢出的缓冲区覆盖。
但是,如果我交换前两行:
char buffer[4];
int i = 0;
strcpy(buffer, "aaaaaaaaaaaaaaa");
printf("%d", i);
输出完全相同。
怎么可能? i
的地址低于 buffer
的地址,因此缓冲区溢出应该会覆盖其他数据,但不会覆盖 i
。还是我遗漏了什么?
局部变量的顺序没有规定,所以编译器通常可以随意分配它们。但另一方面,编译器会使用许多策略来减少您自愿尝试做的事情发生的可能性。
其中一个安全增强措施是分配一个始终远离其他标量变量的缓冲区,因为数组可以越界寻址并且更倾向于膨胀相邻变量。另一个技巧是在数组之后添加一些空陷阱 space 来为边界问题创建一种隔离。
无论如何你可以使用调试器查看程序集以确认变量定位。
如果您想查看编译器如何分配局部变量,请尝试使用 gcc -S
进行编译,这将输出汇编代码。在汇编代码中,您可以看到编译器如何选择对变量进行排序。
在编译器如何选择对局部变量进行排序时要记住的一件事是每个 char 只需要按 1 对齐(这意味着它可以从内存的任何字节开始),另一方面, int必须按 4 对齐(这意味着它只能从可被 4 整除的字节开始),因此根据对齐方式,编译器有自己的逻辑来避免数据为空字节,这意味着它经常将变量组合在一起具有一定顺序的相似类型。所以即使你这样定义它们:
int a;
char c;
int b;
char d;
很可能编译器将内存中的 int 和 char 组合在一起,因此从顶部的低内存到底部的高内存的内存可能类似于:
low memory
| | | char d | char c|
| int b |
| int a |
high memory
每块||代表一个字节,一整行代表4个字节。