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.buf
和 S.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=]
+---+
不用说,n
、t
、[=18=]
这三个字符,很可能会引发更多的问题。
这就是人们说永远不要使用 gets
的原因。不能安全使用。
局部变量一般是在栈上创建的。在大多数实现中,堆栈随着内存的分配而向下增长,而不是向上增长。因此,buf
的地址可能比 x
高。这就是为什么当 buf
溢出时,它不会覆盖 x
.
您可以通过写入 buf[-1]='v';printf("%c\n",x);
来确认这一点,尽管这可能会受到填充的影响。将地址与 printf("%i\n",buf - &x);
进行比较也可能具有指导意义——如果结果为正,则 buf
的地址高于 x
.
但这都高度依赖于实现,并且可以根据各种编译器选项进行更改。正如其他人所说,您不应该依赖其中任何一个。
我试图了解缓冲区溢出。这是我的代码:
#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.buf
和 S.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=]
+---+
不用说,n
、t
、[=18=]
这三个字符,很可能会引发更多的问题。
这就是人们说永远不要使用 gets
的原因。不能安全使用。
局部变量一般是在栈上创建的。在大多数实现中,堆栈随着内存的分配而向下增长,而不是向上增长。因此,buf
的地址可能比 x
高。这就是为什么当 buf
溢出时,它不会覆盖 x
.
您可以通过写入 buf[-1]='v';printf("%c\n",x);
来确认这一点,尽管这可能会受到填充的影响。将地址与 printf("%i\n",buf - &x);
进行比较也可能具有指导意义——如果结果为正,则 buf
的地址高于 x
.
但这都高度依赖于实现,并且可以根据各种编译器选项进行更改。正如其他人所说,您不应该依赖其中任何一个。