C缓冲区溢出的说明

时间:2018-08-24 11:50:17

标签: c buffer-overflow

我尝试了解缓冲区溢出。这是我的代码:

#include <stdio.h>

int main() 
{
    char buf[5] = { 0 };
    char x = 'u';

    printf("Please enter your name: ");
    gets(buf);

    printf("Hello %s!", buf);

    return 0;
}

buf数组的大小为5,并以0es初始化。因此(终止为空)我有四个字符的空间。如果输入五个字符(例如,堆栈),则会覆盖空终止符,并且printf应该显示“ Hello stacku!”。由于后继变量x。但是事实并非如此。它只是打印“堆栈”。有人可以解释为什么吗?

3 个答案:

答案 0 :(得分:12)

简短的解释是,仅仅因为您在源代码行中的'buf'之后声明了'x',但这并不意味着编译器会将它们在堆栈上彼此相邻。在显示的代码中,根本不使用“ x”,因此它可能没有放在任何地方。即使您确实以某种方式使用了“ x”(并且必须采用某种方式来防止将其填充到寄存器中),编译器还是很有可能将其精确地排序为“ ” “ buf”它不会被溢出“ buf”的代码覆盖不会

您可以强制该程序使用struct结构(例如,

)覆盖'x'
#include <stdio.h>

int main() 
{
    struct {
        char buf[5];
        char x[2];
    } S = { { 0 }, { 'u' } };

    printf("Please enter your name: ");
    gets(S.buf);

    printf("Hello %s!\n", S.buf);
    printf("S.x[0] = %02x\n", S.x[0]);

    return 0;
}

因为struct 的字段总是按照它们在源代码中出现的顺序排列在内存中。 1 原则上可以填充在S.bufS.x之间,但是char的对齐要求必须为1,因此ABI可能不需要。

但是即使您执行 操作,它也不会打印'Hello stacku!',因为gets总是写一个终止NUL。观看:

$ ./a.out 
Please enter your name: stac
Hello stac!
S.x[0] = 75

$ ./a.out 
Please enter your name: stack
Hello stack!
S.x[0] = 00

$ ./a.out 
Please enter your name: stacks
Hello stacks!
S.x[0] = 73

看看它总是如何打印您键入的内容,但是x[0]确实会被覆盖,首先是NUL,然后是's'?

(您已经读过Smashing the Stack for Fun and Profit吗?应该。)


1 学徒脚注:如果涉及位字段,则内存中字段的顺序将部分由实现定义。但这对于这个问题而言并不重要。

答案 1 :(得分:6)

正如另一个答案所指出的那样,根本无法保证x会紧随buf后坐在内存中。但是即使这样做, gets也会覆盖它。请记住:gets无法知道目标缓冲区有多大。 (这是它的致命缺陷。)它将始终写入其读取的整个字符串以及结尾的\0。因此,如果x恰好位于buf之后,那么如果您输入五个字符的字符串,printf可能会正确打印(如您所见),并且如果要之后检查x的值:

printf("x = %d = %c\n", x, x);

它可能会告诉您x现在是0,而不是'U'

这是存储器最初的外观:

     +---+---+---+---+---+
buf: |   |   |   |   |   |
     +---+---+---+---+---+

     +---+
  x: | U |
     +---+

因此,在键入“堆栈”后,它看起来像这样:

     +---+---+---+---+---+
buf: | s | t | a | c | k |
     +---+---+---+---+---+

     +---+
  x: |\0 |
     +---+

如果您输入“ elephant”,它将如下所示:

     +---+---+---+---+---+
buf: | e | l | e | p | h |
     +---+---+---+---+---+

     +---+
  x: | a | n   t  \0
     +---+

不用说,这三个字符nt\0可能会引起更多问题。

这就是为什么人们说永远不要使用gets的原因。无法安全使用。

答案 2 :(得分:2)

局部变量通常在堆栈上创建。在大多数实现中,随着分配内存,堆栈向下增长,而不是向上增长。因此,buf的地址可能比x的地址高。因此,当buf溢出时,它不会覆盖x

您也许可以通过写buf[-1]='v';printf("%c\n",x);来确认这一点,尽管这可能会受到填充的影响。将地址与printf("%i\n",buf - &x);进行比较也可能是有启发性的-如果结果为肯定,则buf的地址比x的地址高。

但这全都高度依赖于实现,并且可以根据各种编译器选项进行更改。正如其他人所说,您不应依赖任何这些。