我尝试了解缓冲区溢出。这是我的代码:
#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
。但是事实并非如此。它只是打印“堆栈”。有人可以解释为什么吗?
答案 0 :(得分:12)
简短的解释是,仅仅因为您在源代码行中的'buf'之后声明了'x',但这并不意味着编译器会将它们在堆栈上彼此相邻。在显示的代码中,根本不使用“ x”,因此它可能没有放在任何地方。即使您确实以某种方式使用了“ x”(并且必须采用某种方式来防止将其填充到寄存器中),编译器还是很有可能将其精确地排序为“
您可以强制该程序使用struct
结构(例如,
#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;
}
因为 但是即使您执行 操作,它也不会打印'Hello stacku!',因为 看看它总是如何打印您键入的内容,但是 (您已经读过Smashing the Stack for Fun and Profit吗?应该。) 1 学徒脚注:如果涉及位字段,则内存中字段的顺序将部分由实现定义。但这对于这个问题而言并不重要。struct
的字段总是按照它们在源代码中出现的顺序排列在内存中。 1 原则上可以填充在S.buf
和S.x
之间,但是char
的对齐要求必须为1,因此ABI可能不需要。
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'?
答案 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
+---+
不用说,这三个字符n
,t
和\0
可能会引起更多问题。
这就是为什么人们说永远不要使用gets
的原因。无法安全使用。
答案 2 :(得分:2)
局部变量通常在堆栈上创建。在大多数实现中,随着分配内存,堆栈向下增长,而不是向上增长。因此,buf
的地址可能比x
的地址高。因此,当buf
溢出时,它不会覆盖x
。
您也许可以通过写buf[-1]='v';printf("%c\n",x);
来确认这一点,尽管这可能会受到填充的影响。将地址与printf("%i\n",buf - &x);
进行比较也可能是有启发性的-如果结果为肯定,则buf
的地址比x
的地址高。
但这全都高度依赖于实现,并且可以根据各种编译器选项进行更改。正如其他人所说,您不应依赖任何这些。