GCC堆栈内存分配

3

我对内存布局分配有些困惑,假设我们有:

int flag = 0;
char array[10];

在内存栈中,数组位于标志位之上。

(gdb) x/x array : 0xbffff4bc
(gdb) x/x flag :  0xbffff4cc

然而,假设我们把这些行反过来:
char array[10];
int flag = 0;

然而,分配内存的方式仍然相同。

(gdb) x/x array : 0xbffff4bc
(gdb) x/x flag :  0xbffff4cc

为什么分配操作没有改变?第二个选项的标志不应该在栈中的数组之上吗?即使我使用-fno-stack-protector编译,为什么内存被分配给栈中整数上方的数组? 这是否正确?我正在运行gcc 4.7.2 Debian。
编辑
我刚刚发现了同样问题的帖子memory-allocation-issue-buffer-overflow-attack。但是,在阅读第一个答案时,在评论中指出,使用-fno-stack-protector应该解决该问题(按声明顺序顺序地分配内存)。但在我的情况下无法解决问题。有任何建议吗?

4
编程语言并不保证无关的本地变量在内存中的相对位置。编译器可以自由地重新排列这些变量。所有的排列都是“正确”的。你为什么觉得这很奇怪?你的数组大小是“不均匀”的(10个字节),所以看到编译器移动它们以更好地打包和/或对齐它们并不令人惊讶。 - AnT stands with Russia
我基本上是试图溢出缓冲区并更改标志的内容,因此当我将标志放在缓冲区之上时,该缓冲区溢出不应起作用。 - T-D
你为什么认为你应该能够做到? - n. m.
因为如果标志的内存在缓冲区之前分配,那么缓冲区将位于堆栈中的标志上方。因此,当使用特定参数溢出缓冲区时,标志内存地址中的内容将发生更改。这将使其不同于零,这是我的目标。这个方法很好用。但是,当我反转顺序时,我想先为数组分配内存,然后再为整数分配内存,以防止缓冲区溢出。 - T-D
@T-D:好的,也许我误解了“这将使它与零不同,这就是我的目标”。 - Crowman
显示剩余2条评论
3个回答

3
正如@AndreyT的评论所指出的那样,标准并没有要求编译器必须按照局部变量在源代码中出现的顺序在堆栈上布局它们,或以任何特定的顺序进行布局。您可以通过将它们放入结构体中来强制对它们进行顺序布局,因为标准确实为结构体规定了布局规则(C99 6.7.2.1段13)。即便如此,编译器也可以在元素之间放置填充。
但无论哪种方式,您的策略都是有缺陷的,因为标准还说,在数组边界外写入会引发“未定义行为”,这意味着不能保证它能安全地更新您的标志。不同的架构和/或编译器可能会有不同的响应,可能会导致硬件触发异常或其他致命条件。您不能依赖任何特定的行为。

是的,我只是出于教育目的在尝试缓冲区溢出攻击。我在一本书中找到了一个例子,它说如果反转这两行代码,就会反转它们在堆栈中的内存顺序。但我猜那不是真的... - T-D

2

在对此问题进行更多测试和实验后,一个可行的解决方法是使用-fstack-protector,以便编译器将缓冲区放置在标志变量下方的内存中,从而保护自己免受缓冲区溢出攻击。

char password_buffer[16];
int auth_flag = 0;

当你以这种方式运行它时,
x/x password_buffer: 0xbffff4bc
x/x &auth_flag: 0xbffff4b8

这意味着password_buffer被分配在auth_flag下方的内存中。


0

这是很正常的(但并不保证,正如@harmic所指出的那样),通常会先分配较大的内存块,以避免内存碎片化。


你能否添加证据和相关来源,证明你所声称的“相当正常”的避免堆栈碎片化的做法? - xmojmr
不行,因为我已经无法访问1980年代中期及以后的编译器了。 - Rein

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接