我正在做一些关于缓冲区溢出的研究,我想知道堆栈粉碎保护是如何工作的
我有这段代码:
int main( )
{
char Buf[16];
printf(“Digite o seu nome: ”);
gets(Buf);
printf(“%s”,Buf);
return 0;
}
我用gcc编译它
然后我放入一堆字符来填充缓冲区
首先我放16个字符
$ ./Exemplo1
Digite o seu nome:AAAAAAAAAAAAAAAA
Ola AAAAAAAAAAAAAAAA
这是好的,因为缓冲区大小合适
接下来我尝试24个字符
$ ./Exemplo1
Digite o seu nome:AAAAAAAAAAAAAAAAAAAAAAAA
Ola AAAAAAAAAAAAAAAAAAAAAAAA
为什么它仍然有效?
这不应该导致程序终止!?
当我输入25个或更多字符时,它只会终止程序
./ Exemplo1
Digite o seu nome:AAAAAAAAAAAAAAAAAAAAAAAAA
Ola AAAAAAAAAAAAAAAAAAAAAAAAA
*检测到堆栈粉碎* :./ Exemplo1已终止
为什么呢?什么是缓冲区后不是返回地址? 我读到的以及我认为理解的是它应该有一个金丝雀值,但如果该值已经改变并且将24个字符写入缓冲区它应该终止程序它应该不会给我一个堆栈粉碎检测甚至如果返回地址没有改变但金丝雀值没有改变。
感谢。
答案 0 :(得分:6)
从我对生成的程序集的简要介绍中,我认为这是发生的事情(64位Ubuntu上的gcc 4.4.5)。
金丝雀是双字(8字节)。堆栈 - 包括金丝雀 - 以16字节为增量增长。金丝雀正好位于堆叠框架的末端。为了同时满足所有三个要求,gcc
可能需要在自动变量和金丝雀之间插入填充。
由于代码Buf
长度为16个字节,gcc
在Buf
和金丝雀之间放置了8个字节的填充(需要使堆栈帧的大小为多个16字节)。这就解释了为什么你最多可以输入24个字符而不会触发堆栈粉碎检测。
如果我将Buf
更改为8字节长,则不再需要任何填充,输入9个字符会触发保护:
#include <stdio.h>
int main( )
{
char Buf[8];
printf("Digite o seu nome: ");
gets(Buf);
printf("%s",Buf);
return 0;
}
aix@aix:~$ ./a.out
Digite o seu nome: AAAAAAAA
AAAAAAAA
aix@aix:~$ ./a.out
Digite o seu nome: AAAAAAAAA
*** stack smashing detected ***: ./a.out terminated
显然,这取决于编译器,硬件平台,编译器标志等。
不言而喻,人们不应期望这种机制能够防弹。
如果您想进一步尝试,请尝试使用不同的缓冲区大小和/或-fno-stack-protector
编译代码。如果您使用-S
生成汇编代码,则可以在调整设置时查看生成的代码的更改方式。
答案 1 :(得分:0)
编译器不保证如何在堆栈上组织数据。缓冲区buf[]
可以分配在关键数据旁边,如金丝雀,旧堆栈指针和返回地址;或者两者之间可能有一些额外的空间。在这种情况下,看起来好像有一些填充。
答案 2 :(得分:0)
缓冲区的实际大小可能大于您指定的大小,或者缓冲区旁边的堆栈上有更多大小。通过超出缓冲区边界,您可以写入缓冲区中的额外内存,也可以覆盖堆栈的其他内容。这不好,因为它可能会丢弃一些局部变量,但它不会覆盖返回地址。
只有在写入已分配的本地堆栈帧并覆盖当前正在执行的函数的返回地址时,才会发生错误。如果你很幸运,你的程序就会崩溃。但究竟会发生什么事情很难预见。
答案 3 :(得分:0)
目前大多数编译器都包含堆栈cookie或其他形式的堆栈保护。可能这是ProPolice的工作http://en.wikipedia.org/wiki/Buffer_overflow_protection#GCC_Stack-Smashing_Protector_.28ProPolice.29