C缓冲区溢出的解释

Explanation of 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,初始化为 0。所以(空终止)我有四个字符的 space 。如果我输入五个字符(例如堆栈),我会覆盖空终止字符并且 printf 应该打印 "Hello stacku!" 因为后续变量 x。但事实并非如此。它只是打印 "stack"。有人可以解释一下为什么吗?

简短的解释是,仅仅因为您在 'buf' 之后的源代码行中声明了 'x',并不意味着编译器将它们放在堆栈中并排放置。对于显示的代码,'x' 根本没有被使用,所以它可能没有被放在 任何地方 。即使你确实以某种方式使用了 'x'(而且它必须是一种防止它被塞入寄存器的方法),编译器也很有可能将它排序 below '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 学究脚注:如果涉及位域,则内存中的域顺序将部分由实现定义。但这对于这个问题的目的并不重要。

正如另一个答案所指出的,完全不能保证 x 会在内存中紧跟在 buf 之后。但即使它这样做了:gets 将覆盖它。请记住:gets 无法知道目标缓冲区有多大。 (这是它的致命缺陷。)它总是写入它读取的整个字符串,加上终止符 [=18=]。因此,如果 x 恰好紧跟在 buf 之后,那么如果您键入一个由五个字符组成的字符串,printf 可能会正确打印(如您所见),并且如果您要之后检查 x 的值:

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

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

内存最初可能是这样的:

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

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

所以在你输入 "stack" 之后,它看起来像这样:

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

     +---+
  x: |[=12=] |
     +---+

如果你输入 "elephant" 它将看起来像这样:

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

     +---+
  x: | a | n   t  [=13=]
     +---+

不用说,nt[=18=]这三个字符,很可能会引发更多的问题。

这就是人们说永远不要使用 gets 的原因。不能安全使用。

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

您可以通过写入 buf[-1]='v';printf("%c\n",x); 来确认这一点,尽管这可能会受到填充的影响。将地址与 printf("%i\n",buf - &x); 进行比较也可能具有指导意义——如果结果为正,则 buf 的地址高于 x.

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