无法获取分段错误



我从Numberphile视频中看到(https://youtu.be/1S0aBV-Waeo)一种运行缓冲区溢出的方法,我想尝试一下。我写了一段代码,它与视频中显示的代码完全相同,只是大小为"0";缓冲区";,但是如果我输入一个大于"0"大小的字符串;缓冲区";,我没有像视频中显示的那样出现分割错误;有人能解释一下原因吗?

#include <stdio.h>
#include <string.h>
int main(int argc, char** argv){
char buffer[50];
strcpy(buffer, argv[1]);

return 0;
}

编辑:顺便说一句,正如我在评论中看到的那样,这是一件决定性的事情,我正在使用GCC编译器。

如视频所示,我没有遇到分割错误;有人能解释一下原因吗?

程序有未定义的行为,因为您输入的字符串大于buffer的大小,并且来自strcpy文档:

为避免溢出,目标指向的数组的大小应足够长,以包含与源相同的C字符串(包括终止的null字符),并且在内存中不应与源重叠。

(强调矿)


未定义的行为意味着任何1都可能发生,包括但不限于提供预期输出的程序。但是永远不要依赖(或根据未定义行为的程序的输出得出结论)。程序可能会崩溃。

所以您看到的输出(可能看到的)是未定义行为的结果。正如我所说,不要依赖于有UB的程序的输出。程序可能会崩溃。

因此,使程序正确的第一步是删除UB然后并且只有到那时您才能开始对程序的输出进行推理。


1对于未定义行为的更准确的技术定义,请参阅此处,其中提到:对程序的行为没有限制

如果我认为你想了解具体案例中发生了什么是正确的,你可以通过提供编译器的版本、传递给编译器的参数、传递给程序的参数以及程序的输出来改进你的问题。这样,你就会有一个最小可复制的例子,我们会更好地了解你的具体情况

例如,我使用GCC 9.4.0:

$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

以下是当我在没有优化的情况下编译并将一个包含55个字符的字符串作为参数传递给程序时发生的情况:

$ gcc -o bufferoverflow bufferoverflow.c
$ ./bufferoverflow 1234567890123456789012345678901234567890123456789012345
$

因此,即使复制到缓冲区的字节数,包括终止符在内的56,应该会导致写入超过缓冲区的末尾,但程序运行时没有任何错误,只需查看标准错误或标准输出即可看到这些错误。

以下是当我运行相同的可执行文件,但在命令行中传递了一个57个字符的字符串时发生的情况。

$ ./bufferoverflow 123456789012345678901234567890123456789012345678901234567
*** stack smashing detected ***: terminated
Aborted (core dumped)
$

了解55个字符字符串的情况的一种方法是使用gdb再次运行它,它可以如图所示启动:

$ gdb bufferoverflow
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from bufferoverflow...
(No debugging symbols found in bufferoverflow)
(gdb)

现在让我们看看为什么将55个字符的字符串作为第一个参数传递不会导致明显的失败:

(gdb) break main
Breakpoint 1 at 0x1169
(gdb) r 1234567890123456789012345678901234567890123456789012345
Starting program: /home/tim/bufferoverflow 1234567890123456789012345678901234567890123456789012345
Breakpoint 1, 0x0000555555555169 in main ()
(gdb) x/23i main
=> 0x555555555169 <main>:   endbr64 
0x55555555516d <main+4>: push   %rbp
0x55555555516e <main+5>: mov    %rsp,%rbp
0x555555555171 <main+8>: sub    $0x50,%rsp
0x555555555175 <main+12>:    mov    %edi,-0x44(%rbp)
0x555555555178 <main+15>:    mov    %rsi,-0x50(%rbp)
0x55555555517c <main+19>:    mov    %fs:0x28,%rax
0x555555555185 <main+28>:    mov    %rax,-0x8(%rbp)
0x555555555189 <main+32>:    xor    %eax,%eax
0x55555555518b <main+34>:    mov    -0x50(%rbp),%rax
0x55555555518f <main+38>:    add    $0x8,%rax
0x555555555193 <main+42>:    mov    (%rax),%rdx
0x555555555196 <main+45>:    lea    -0x40(%rbp),%rax
0x55555555519a <main+49>:    mov    %rdx,%rsi
0x55555555519d <main+52>:    mov    %rax,%rdi
0x5555555551a0 <main+55>:    callq  0x555555555060 <strcpy@plt>
0x5555555551a5 <main+60>:    mov    $0x0,%eax
0x5555555551aa <main+65>:    mov    -0x8(%rbp),%rcx
0x5555555551ae <main+69>:    xor    %fs:0x28,%rcx
0x5555555551b7 <main+78>:    je     0x5555555551be <main+85>
0x5555555551b9 <main+80>:    callq  0x555555555070 <__stack_chk_fail@plt>
0x5555555551be <main+85>:    leaveq 
0x5555555551bf <main+86>:    retq   

从上面的反汇编中,我们可以看到main+60就在调用strcpy之后。通过查看main+45main+52,我们还可以看到缓冲区位于%rbp-0x40。我们可以继续到这一点,看看缓冲区发生了什么:

(gdb) b *(main+60)
Breakpoint 2 at 0x5555555551a5
(gdb) c
Continuing.
Breakpoint 2, 0x00005555555551a5 in main ()
(gdb) x/56bx $rbp-0x40
0x7fffffffdf90: 0x31    0x32    0x33    0x34    0x35    0x36    0x37    0x38
0x7fffffffdf98: 0x39    0x30    0x31    0x32    0x33    0x34    0x35    0x36
0x7fffffffdfa0: 0x37    0x38    0x39    0x30    0x31    0x32    0x33    0x34
0x7fffffffdfa8: 0x35    0x36    0x37    0x38    0x39    0x30    0x31    0x32
0x7fffffffdfb0: 0x33    0x34    0x35    0x36    0x37    0x38    0x39    0x30
0x7fffffffdfb8: 0x31    0x32    0x33    0x34    0x35    0x36    0x37    0x38
0x7fffffffdfc0: 0x39    0x30    0x31    0x32    0x33    0x34    0x35    0x00

因此,我们可以看到,尽管我们早些时候在没有gdb的情况下使用此字符串运行时没有注意到任何明显的错误,但实际上确实发生了缓冲区溢出。我们根本没有注意到它有。为了理解为什么我们没有注意到,我们只需要查看反汇编,就可以看到堆栈上下一个使用的地址位于%rbp-8,即%rbp-0x40之后的56个字节。因此,溢出进入了未使用的内存。

同样的反汇编说明了当我们使用57个字符的字符串运行程序时,为什么会得到检测到堆栈粉碎消息。在这种情况下,我们截取%rbp-8处的8字节值的一部分,该值用于检查堆栈在调用main期间是否损坏。因此,我们在该特定输入中看到该特定错误的原因是,%rbp-8处的8字节值是堆栈中唯一一个在我们删除后实际使用的部分,而有问题的消息是由于注意到这8个字节已经更改。

即使你没有像我那样编译你的程序,即使你没有使用完全相同的输入,我希望我已经给了你一些关于如何理解你的行为的坚实的想法。

最新更新