当前位置:  开发笔记 > 编程语言 > 正文

检测到堆栈粉碎

如何解决《检测到堆栈粉碎》经验,为你挑选了4个好方法。

我正在执行我的a.out文件.执行后程序运行一段时间然后退出并显示以下消息:

**** stack smashing detected ***: ./a.out terminated*
*======= Backtrace: =========*
*/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)Aborted*

可能的原因是什么?如何纠正?



1> sud03r..:

这里的Stack Smashing实际上是由于gcc用来检测缓冲区溢出错误的保护机制引起的.例如,在以下代码段中:

#include 

void func()
{
    char array[10];
    gets(array);
}

int main(int argc, char **argv)
{
    func();
}

编译器(在本例中为gcc)添加了具有已知值的保护变量(称为canaries).大小大于10的输入字符串会导致此变量损坏,从而导致SIGABRT终止程序.

为了获得一些见解,您可以尝试 -fno-stack-protector 在编译时使用选项禁用gcc的这种保护 .在这种情况下,当您尝试访问非法内存位置时,您将收到不同的错误,很可能是分段错误.请注意,-fstack-protector应始终为发布版本打开,因为它是一种安全功能.

您可以通过使用调试器运行程序来获取有关溢出点的一些信息.Valgrind与堆栈相关的错误不能很好地工作,但是像调试器一样,它可以帮助您精确定位崩溃的位置和原因.


OP询问这种行为的可能原因,我的回答提供了一个例子以及它与一个合理已知错误的关系.此外,删除堆栈保护器不是一个解决方案,它可以做一些实验,以获得更多的问题见解.建议实际上是以某种方式修复错误,感谢指向valgrind,我将编辑我的答案以反映这一点.
这个答案是不正确的,并提供危险的建议.首先,删除堆栈保护程序不是正确的解决方案 - 如果您遇到堆栈碎片错误,您的代码中可能存在严重的安全漏洞.正确的答案是*修复错误的代码*.其次,正如grasGendarme指出的那样,尝试Valgrind的建议不会有效.Valgrind通常不能检测对堆栈分配数据的非法内存访问.
Valgrind不适用于与堆栈相关的错误,因为它无法在那里添加红色区域
@DW应该在发布版本中关闭堆栈保护,因为首先 - *stack smashing detected*消息仅对开发人员有帮助; 第二 - 申请可能还有机会生存; 而在第三 - 这是一个很小的优化.
谢谢你的回答!我发现在我的情况下我没有初始化我试图写的变量
在Linux上的GCC或Clang中使用`-fsanitize = address`应该会为您提供有关溢出的非常详细的信息。

2> Ciro Santill..:

拆卸分析的最小示例

main.c中

void myfunc(char *const src, int len) {
    int i;
    for (i = 0; i < len; ++i) {
        src[i] = 42;
    }
}

int main(void) {
    char arr[] = {'a', 'b', 'c', 'd'};
    int len = sizeof(arr);
    myfunc(arr, len + 1);
    return 0;
}

编译并运行:

gcc -fstack-protector -g -O0 -std=c99 main.c
ulimit -c unlimited && rm -f core
./a.out

失败了:

*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)

在Ubuntu 16.04,GCC 6.4.0上测试.

拆卸

现在我们来看看反汇编:

objdump -D a.out

其中包含:

int main (void){
  400579:       55                      push   %rbp
  40057a:       48 89 e5                mov    %rsp,%rbp

  # Allocate 0x10 of stack space.
  40057d:       48 83 ec 10             sub    $0x10,%rsp

  # Put the 8 byte canary from %fs:0x28 to -0x8(%rbp),
  # which is right at the bottom of the stack.
  400581:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  400588:       00 00 
  40058a:       48 89 45 f8             mov    %rax,-0x8(%rbp)

  40058e:       31 c0                   xor    %eax,%eax
    char arr[] = {'a', 'b', 'c', 'd'};
  400590:       c6 45 f4 61             movb   $0x61,-0xc(%rbp)
  400594:       c6 45 f5 62             movb   $0x62,-0xb(%rbp)
  400598:       c6 45 f6 63             movb   $0x63,-0xa(%rbp)
  40059c:       c6 45 f7 64             movb   $0x64,-0x9(%rbp)
    int len = sizeof(arr);
  4005a0:       c7 45 f0 04 00 00 00    movl   $0x4,-0x10(%rbp)
    myfunc(arr, len + 1);
  4005a7:       8b 45 f0                mov    -0x10(%rbp),%eax
  4005aa:       8d 50 01                lea    0x1(%rax),%edx
  4005ad:       48 8d 45 f4             lea    -0xc(%rbp),%rax
  4005b1:       89 d6                   mov    %edx,%esi
  4005b3:       48 89 c7                mov    %rax,%rdi
  4005b6:       e8 8b ff ff ff          callq  400546 
    return 0;
  4005bb:       b8 00 00 00 00          mov    $0x0,%eax
}
  # Check that the canary at -0x8(%rbp) hasn't changed after calling myfunc.
  # If it has, jump to the failure point __stack_chk_fail.
  4005c0:       48 8b 4d f8             mov    -0x8(%rbp),%rcx
  4005c4:       64 48 33 0c 25 28 00    xor    %fs:0x28,%rcx
  4005cb:       00 00 
  4005cd:       74 05                   je     4005d4 
  4005cf:       e8 4c fe ff ff          callq  400420 <__stack_chk_fail@plt>

  # Otherwise, exit normally.
  4005d4:       c9                      leaveq 
  4005d5:       c3                      retq   
  4005d6:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  4005dd:       00 00 00 

注意通过自动添加得心应手意见objdump的人工智能模块.

如果您通过GDB多次运行此程序,您将看到:

金丝雀每次都会获得不同的随机值

最后一个循环myfunc正好修改了金丝雀的地址

金丝雀通过设置随机化%fs:0x28,其中包含随机值,如下所述:

https://unix.stackexchange.com/questions/453749/what-sets-fs0x28-stack-canary

为什么这个内存地址%fs:0x28(fs [0x28])有一个随机值?

调试尝试

从现在开始,我们修改代码:

    myfunc(arr, len + 1);

相反:

    myfunc(arr, len);
    myfunc(arr, len + 1); /* line 12 */
    myfunc(arr, len);

更有趣.

然后,我们将尝试查看是否可以+ 1使用比仅仅阅读和理解整个源代码更自动化的方法来查明罪魁祸首.

gcc -fsanitize=address

如果使用此标志重新编译并运行该程序,则输出:

#0 0x4008bf in myfunc /home/ciro/test/main.c:4
#1 0x40099b in main /home/ciro/test/main.c:12
#2 0x7fcd2e13d82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#3 0x400798 in _start (/home/ciro/test/a.out+0x40079

然后是一些更彩色的输出.

这清楚地指出了有问题的线12.

感谢Google.

Valgrind SGCheck

正如其他人所说,Valgrind并不擅长解决这类问题.

它有一个名为SGCheck的实验工具:

SGCheck是一种用于查找堆栈和全局数组溢出的工具.它的工作原理是使用一种启发式方法,该方法源于对可能的堆栈形式和全局数组访问的观察.

所以当它没有找到错误时我并不感到惊讶:

valgrind --tool=exp-sgcheck ./a.out

错误消息应该看起来像这样:Valgrind错过了错误

GDB

一个重要的观察是,如果您通过GDB运行程序,或在core事后检查文件:

gdb -nh -q a.out core

然后,正如我们在程序集上看到的那样,GDB应该指向执行金丝雀检查的函数的结尾:

(gdb) bt
#0  0x00007f0f66e20428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007f0f66e2202a in __GI_abort () at abort.c:89
#2  0x00007f0f66e627ea in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7f0f66f7a49f "*** %s ***: %s terminated\n") at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007f0f66f0415c in __GI___fortify_fail (msg=, msg@entry=0x7f0f66f7a481 "stack smashing detected") at fortify_fail.c:37
#4  0x00007f0f66f04100 in __stack_chk_fail () at stack_chk_fail.c:28
#5  0x00000000004005f6 in main () at main.c:15
(gdb) f 5
#5  0x00000000004005f6 in main () at main.c:15
15      }
(gdb)

因此问题可能出在这个函数的一个调用中.

接下来,我们尝试通过在设置金丝雀之后第一次单步升级来确定确切的失败呼叫:

  400581:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  400588:       00 00 
  40058a:       48 89 45 f8             mov    %rax,-0x8(%rbp)

并看着地址:

(gdb) p $rbp - 0x8
$1 = (void *) 0x7fffffffcf18
(gdb) watch 0x7fffffffcf18
Hardware watchpoint 2: *0x7fffffffcf18
(gdb) c
Continuing.

Hardware watchpoint 2: *0x7fffffffcf18

Old value = 1800814336
New value = 1800814378
myfunc (src=0x7fffffffcf14 "*****?Vk\266", , len=5) at main.c:3
3           for (i = 0; i < len; ++i) {
(gdb) p len
$2 = 5
(gdb) p i
$3 = 4
(gdb) bt
#0  myfunc (src=0x7fffffffcf14 "*****?Vk\266", , len=5) at main.c:3
#1  0x00000000004005cc in main () at main.c:12

现在,这确实让我们处于正确的违规指令中:len = 5并且i = 4,在这种特殊情况下,确实将我们指向了罪魁祸首第12行.

但是,回溯已损坏,并包含一些垃圾.正确的回溯看起来像:

#0  myfunc (src=0x7fffffffcf14 "abcd", len=4) at main.c:3
#1  0x00000000004005b8 in main () at main.c:11

所以这可能会破坏堆栈并阻止您看到跟踪.

此外,此方法需要知道金丝雀检查功能的最后一次调用,否则您将有误报,除非您使用反向调试,否则这些误报并不总是可行.



3> wearetherock..:

请看以下情况:

ab@cd-x:$ cat test_overflow.c 
#include 
#include 

int check_password(char *password){
    int flag = 0;
    char buffer[20];
    strcpy(buffer, password);

    if(strcmp(buffer, "mypass") == 0){
        flag = 1;
    }
    if(strcmp(buffer, "yourpass") == 0){
        flag = 1;
    }
    return flag;
}

int main(int argc, char *argv[]){
    if(argc >= 2){
        if(check_password(argv[1])){
            printf("%s", "Access granted\n");
        }else{
            printf("%s", "Access denied\n");
        }
    }else{
        printf("%s", "Please enter password!\n");
    }
}
ab@cd-x:$ gcc -g -fno-stack-protector test_overflow.c 
ab@cd-x:$ ./a.out mypass
Access granted
ab@cd-x:$ ./a.out yourpass
Access granted
ab@cd-x:$ ./a.out wepass
Access denied
ab@cd-x:$ ./a.out wepassssssssssssssssss
Access granted

ab@cd-x:$ gcc -g -fstack-protector test_overflow.c 
ab@cd-x:$ ./a.out wepass
Access denied
ab@cd-x:$ ./a.out mypass
Access granted
ab@cd-x:$ ./a.out yourpass
Access granted
ab@cd-x:$ ./a.out wepassssssssssssssssss
*** stack smashing detected ***: ./a.out terminated
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)[0xce0ed8]
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x0)[0xce0e90]
./a.out[0x8048524]
./a.out[0x8048545]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xc16b56]
./a.out[0x8048411]
======= Memory map: ========
007d9000-007f5000 r-xp 00000000 08:06 5776       /lib/libgcc_s.so.1
007f5000-007f6000 r--p 0001b000 08:06 5776       /lib/libgcc_s.so.1
007f6000-007f7000 rw-p 0001c000 08:06 5776       /lib/libgcc_s.so.1
0090a000-0090b000 r-xp 00000000 00:00 0          [vdso]
00c00000-00d3e000 r-xp 00000000 08:06 1183       /lib/tls/i686/cmov/libc-2.10.1.so
00d3e000-00d3f000 ---p 0013e000 08:06 1183       /lib/tls/i686/cmov/libc-2.10.1.so
00d3f000-00d41000 r--p 0013e000 08:06 1183       /lib/tls/i686/cmov/libc-2.10.1.so
00d41000-00d42000 rw-p 00140000 08:06 1183       /lib/tls/i686/cmov/libc-2.10.1.so
00d42000-00d45000 rw-p 00000000 00:00 0 
00e0c000-00e27000 r-xp 00000000 08:06 4213       /lib/ld-2.10.1.so
00e27000-00e28000 r--p 0001a000 08:06 4213       /lib/ld-2.10.1.so
00e28000-00e29000 rw-p 0001b000 08:06 4213       /lib/ld-2.10.1.so
08048000-08049000 r-xp 00000000 08:05 1056811    /dos/hacking/test/a.out
08049000-0804a000 r--p 00000000 08:05 1056811    /dos/hacking/test/a.out
0804a000-0804b000 rw-p 00001000 08:05 1056811    /dos/hacking/test/a.out
08675000-08696000 rw-p 00000000 00:00 0          [heap]
b76fe000-b76ff000 rw-p 00000000 00:00 0 
b7717000-b7719000 rw-p 00000000 00:00 0 
bfc1c000-bfc31000 rw-p 00000000 00:00 0          [stack]
Aborted
ab@cd-x:$ 

当我禁用堆栈粉碎保护器时没有检测到错误,这应该发生在我使用"./a.out wepassssssssssssssssssss"时

因此,为了回答上面的问题,显示消息"**stack smashing detected:xxx",因为您的堆栈粉碎保护器处于活动状态,并且发现程序中存在堆栈溢出.

只需找出发生的位置,并修复它.



4> hlovdal..:

您可以尝试使用valgrind调试问题:

Valgrind发行版目前包括六种生产质量工具:内存错误检测器,两个线程错误检测器,一个缓存和分支预测分析器,一个调用图生成缓存分析器和一个堆分析器.它还包括两个实验工具: 堆/堆栈/全局数组溢出检测器和SimPoint基本块矢量生成器.它运行在以下平台上:X86/Linux,AMD64/Linux,PPC32/Linux,PPC64/Linux和X86/Darwin(Mac OS X).


我们怎么能使用那个*堆栈阵列溢出检测器*?你能详细说说吗?
是的,但Valgrind不适用于堆栈分配缓冲区的溢出,这是此错误消息指示的情况.
推荐阅读
360691894_8a5c48
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有