printf() 影响缓冲区溢出情况
printf() affecting buffer overflow situation
我有一个简单的程序,初始化一个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: [=14=]"
LC1:
.ascii "%c[=14=]"
.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 , %esp
call ___main
movl 53719636, 25(%esp)
movw 6, 29(%esp)
movb , 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 19043144, (%eax)
movl 70078063, 4(%eax)
movl 81362, 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 [=14=], %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: [=15=]"
LC1:
.ascii "%c[=15=]"
.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 , %esp
call ___main
movl 53719636, 26(%esp)
movw 6, 30(%esp)
movb , 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 19043144, (%eax)
movl 70078063, 4(%eax)
movl 81362, 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 [=15=], %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
首先让我们看看为什么缓冲区溢出没有在第二个例子中发生。
查看您的输出:
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 53719636, 25(%esp)
movw 6, 29(%esp)
movb , 31(%esp)
x
变量肯定在 31(%esp)
,因为我们看到 'X'
的十进制 ASCII 值被放在那里。
假设 5 个字符的字符串 "Testt"
存储在 25(%esp)
并没有太大的飞跃,因为 25(%esp)
和 31(%esp)
之间的距离是刚好足以存储 5 个字符和一个空终止符。
所以我们知道 str
在 25(%esp)
并且 x
在 31(%esp)
。堆栈应该类似于:
esp +25 +31
| | |
----------------------
| | T e s t t | X |
----------------------
现在我们可以很容易地看到 str
出现在 x
之前并且很清楚为什么写到 str
的末尾会导致 x
被覆盖.
现在是主要问题,为什么这在第一种情况下有效而在第二种情况下无效?
出于某种原因,编译器决定在第一个示例中将 x
放在 str
之后,在第二个示例中将 x
放在 str
之前。
正如评论中所指出的,局部变量在堆栈上的确切位置不是由 C 定义的。编译器可以决定它想要的东西存储的顺序,并且可能会在程序之间更改该顺序-显而易见的原因。
本质上,局部变量在堆栈上的确切位置和顺序是未定义的,因此未定义的行为是缓冲区溢出在一种情况下起作用而在另一种情况下不起作用的原因。
我有一个简单的程序,初始化一个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: [=14=]"
LC1:
.ascii "%c[=14=]"
.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 , %esp
call ___main
movl 53719636, 25(%esp)
movw 6, 29(%esp)
movb , 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 19043144, (%eax)
movl 70078063, 4(%eax)
movl 81362, 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 [=14=], %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: [=15=]"
LC1:
.ascii "%c[=15=]"
.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 , %esp
call ___main
movl 53719636, 26(%esp)
movw 6, 30(%esp)
movb , 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 19043144, (%eax)
movl 70078063, 4(%eax)
movl 81362, 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 [=15=], %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
首先让我们看看为什么缓冲区溢出没有在第二个例子中发生。
查看您的输出:
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 53719636, 25(%esp)
movw 6, 29(%esp)
movb , 31(%esp)
x
变量肯定在 31(%esp)
,因为我们看到 'X'
的十进制 ASCII 值被放在那里。
假设 5 个字符的字符串 "Testt"
存储在 25(%esp)
并没有太大的飞跃,因为 25(%esp)
和 31(%esp)
之间的距离是刚好足以存储 5 个字符和一个空终止符。
所以我们知道 str
在 25(%esp)
并且 x
在 31(%esp)
。堆栈应该类似于:
esp +25 +31
| | |
----------------------
| | T e s t t | X |
----------------------
现在我们可以很容易地看到 str
出现在 x
之前并且很清楚为什么写到 str
的末尾会导致 x
被覆盖.
现在是主要问题,为什么这在第一种情况下有效而在第二种情况下无效?
出于某种原因,编译器决定在第一个示例中将 x
放在 str
之后,在第二个示例中将 x
放在 str
之前。
正如评论中所指出的,局部变量在堆栈上的确切位置不是由 C 定义的。编译器可以决定它想要的东西存储的顺序,并且可能会在程序之间更改该顺序-显而易见的原因。
本质上,局部变量在堆栈上的确切位置和顺序是未定义的,因此未定义的行为是缓冲区溢出在一种情况下起作用而在另一种情况下不起作用的原因。