带有堆栈保护和缓冲区溢出的问题

3
我是一名有用的助手,可以为您翻译文本。
我正在研究缓冲区溢出问题,并想知道堆栈保护如何工作。以下是相关代码:
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

* stack smashing detected *: ./Exemplo1 终止

为什么?在缓冲区之后有什么不是返回地址的东西?我读到的和我理解的是它应该有一个canary值,但如果那个值已经改变,它应该终止程序,并且用24个字符写入缓冲区,即使返回地址没有改变,但canary值确实改变了,它仍然不会给我一个stack smashing detected。

谢谢。


这是你所有的代码吗?"Ola" 是从哪里来的? - diedthreetimes
抱歉,我修改了代码但没有意识到 =/ 但我只是将printf(“%s \ n”,Buf)更改为 printf(“\ n \ nOla%s \ n”,Buf) - Renato
4个回答

6

根据我对生成的汇编代码的简要阅读(在64位Ubuntu上使用gcc 4.4.5),以下是我的理解:

栈的大小以16字节为增量增长,包括canary在内的canary是一个双字(8个字节)。canary被放置在栈帧的最后。为了同时满足这三个条件,gcc可能需要在自动变量和canary之间插入填充。

由于在您的代码中Buf长度为16字节,gccBuf和canary之间放置了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 生成汇编代码,您将能够看到如何在调整设置时生成的代码发生变化。


是的,将缓冲区大小更改为8有效。 我不知道堆栈会以16字节的增量增长,我以为只是缓冲区,其余部分会根据需要分配...对于x64处理器,它总是16字节吗? 顺便说一句,非常感谢=D - Renato
这是否意味着返回地址将从缓冲区开始后的32字节处开始? - Renato
@Renato:关于你的第一个问题,-mpreferred-stack-boundary gcc选项提供了一些灵活性。至于第二个问题,我鼓励你生成并阅读汇编代码。 - NPE

0
编译器不能保证数据在堆栈上的组织方式。缓冲区buf[]可能会被分配到关键数据(如canary、旧堆栈指针和返回地址)旁边,也可能在它们之间有一些额外的空间。在这种情况下,看起来可能有一些填充物。

0

缓冲区的实际大小可能比您指定的大小要大,或者除了缓冲区之外还有更多的堆栈。当越过缓冲区边界时,您要么写入缓冲区中额外的内存,要么覆盖堆栈的其他内容。这不好,因为它可能会破坏一些本地变量,但它还没有覆盖返回地址。

只有当您写入超出分配的本地堆栈帧并覆盖当前执行的函数的返回地址时,才会发生糟糕的事情。如果您很幸运,程序只会崩溃。但确切会发生什么是难以预见的。


0

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