我有一个简单的程序,用于初始化c样式字符串,然后初始化一个字符。然后我使用函数strcpy
导致缓冲区溢出情况,这似乎会覆盖字符变量x
的内存内容(假设它存储在相邻的内存中)。
char str[] = "Testt";
char x = 'X';
// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);
// print value of x
printf("%c\n", x);
// cause buffer overflow
strcpy(str, "Hello world");
// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);
// print address and value of x
// printf("%p: ", &x);
printf("%c\n", x);
return 0;
运行时,此代码生成类似于
的输出0061FF29: Testt
X
0061FF29: Hello world
w
这种情况表明发生了缓冲区溢出,并导致x
变量的值从'X'
更改为'w'
。
但是,如果我删除第三行到最后一行的注释// printf("%p: ", &x);
,则缓冲区溢出不会导致x
变量被覆盖。
为清楚起见,这里是代码(注意第三行到最后一行的变化)
char str[] = "Testt";
char x = 'X';
// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);
// print value of x
printf("%c\n", x);
// cause buffer overflow
strcpy(str, "Hello world");
// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);
// print address and value of x
printf("%p: ", &x);
printf("%c\n", x);
return 0;
这会导致输出为:
0061FF2A: Testt
X
0061FF2A: Hello world
0061FF29: X
因此在这种情况下,缓冲区溢出不会覆盖x
变量。
为什么简单地打印x
变量的内存地址会对缓冲区溢出情况产生影响?
编辑:在两个情况的程序集中添加 为第一种情况生成的程序集(无printf):
.file "hello.c"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%p: \0"
LC1:
.ascii "%c\12\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB17:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call ___main
movl $1953719636, 25(%esp)
movw $116, 29(%esp)
movb $88, 31(%esp)
leal 25(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
leal 25(%esp), %eax
movl %eax, (%esp)
call _puts
movsbl 31(%esp), %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
leal 25(%esp), %eax
movl $1819043144, (%eax)
movl $1870078063, 4(%eax)
movl $6581362, 8(%eax)
leal 25(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
leal 25(%esp), %eax
movl %eax, (%esp)
call _puts
movsbl 31(%esp), %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE17:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
.def _printf; .scl 2; .type 32; .endef
.def _puts; .scl 2; .type 32; .endef
和第二种情况
.file "hello.c"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%p: \0"
LC1:
.ascii "%c\12\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB17:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call ___main
movl $1953719636, 26(%esp)
movw $116, 30(%esp)
movb $88, 25(%esp)
leal 26(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
leal 26(%esp), %eax
movl %eax, (%esp)
call _puts
movzbl 25(%esp), %eax
movsbl %al, %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
leal 26(%esp), %eax
movl $1819043144, (%eax)
movl $1870078063, 4(%eax)
movl $6581362, 8(%eax)
leal 26(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
leal 26(%esp), %eax
movl %eax, (%esp)
call _puts
leal 25(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
movzbl 25(%esp), %eax
movsbl %al, %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE17:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
.def _printf; .scl 2; .type 32; .endef
.def _puts; .scl 2; .type 32; .endef
答案 0 :(得分:1)
首先让我们看看第二个例子中缓冲区溢出没有发生的原因。
看看你的输出:
0061FF2A: Testt
X
0061FF2A: Hello world
0061FF29: X
我们可以看到堆栈上str
x
字符串"Hello world"
占用内存地址0061FF2A
到0061FF36
堆栈看起来像
0061FF29 0061FF2A 0061FF36
| | |
----------------------------
| X | H e l l o w o r l d |
----------------------------
在这种情况下,由于str
位于堆栈上的x
之前,我们写str
的末尾并不重要。
接下来让我们看看第一个例子中缓冲区溢出发生的原因。
我们无法直接在输出中看到每个变量的地址但是我们可以在程序集的堆栈中看到它们的位置。
movl $1953719636, 25(%esp)
movw $116, 29(%esp)
movb $88, 31(%esp)
x
变量肯定在31(%esp)
,因为我们看到'X'
的小数ASCII值被放置在那里。
由于"Testt"
和25(%esp)
之间的距离足够,假设5 25(%esp)
字符串存储在31(%esp)
,这并不是太大的飞跃存储5个字符和一个空终止符。
因此,我们知道str
位于25(%esp)
且x
位于31(%esp)
。堆栈应该类似于:
esp +25 +31
| | |
----------------------
| | T e s t t | X |
----------------------
现在我们可以很容易地看到str
出现在x
之前,很清楚地看到为什么写str
的结尾会导致x
被覆盖。< / p>
现在主要问题是,为什么这在第一种情况下起作用而在第二种情况下不起作用?
由于某种原因,编译器决定在第一个示例中x
之后放置str
,在第二个示例中放置x
str
。
正如在注释中指出的那样,堆栈上的局部变量的确切位置不是由C定义的。编译器可以决定它想要存储的顺序,并且可以在程序之间改变该顺序,以便非显而易见的原因。
本质上,堆栈上局部变量的确切位置和排序是未定义的,因此未定义的行为是缓冲区溢出在一种情况下工作但不在另一种情况下工作的原因。