我正在执行我的a.out文件.执行后程序运行一段时间然后退出并显示以下消息:
**** stack smashing detected ***: ./a.out terminated* *======= Backtrace: =========* */lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)Aborted*
可能的原因是什么?如何纠正?
这里的Stack Smashing实际上是由于gcc用来检测缓冲区溢出错误的保护机制引起的.例如,在以下代码段中:
#includevoid func() { char array[10]; gets(array); } int main(int argc, char **argv) { func(); }
编译器(在本例中为gcc)添加了具有已知值的保护变量(称为canaries).大小大于10的输入字符串会导致此变量损坏,从而导致SIGABRT终止程序.
为了获得一些见解,您可以尝试 -fno-stack-protector
在编译时使用选项禁用gcc的这种保护 .在这种情况下,当您尝试访问非法内存位置时,您将收到不同的错误,很可能是分段错误.请注意,-fstack-protector
应始终为发布版本打开,因为它是一种安全功能.
您可以通过使用调试器运行程序来获取有关溢出点的一些信息.Valgrind与堆栈相关的错误不能很好地工作,但是像调试器一样,它可以帮助您精确定位崩溃的位置和原因.
拆卸分析的最小示例
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 400546return 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 4005d44005cf: 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
所以这可能会破坏堆栈并阻止您看到跟踪.
此外,此方法需要知道金丝雀检查功能的最后一次调用,否则您将有误报,除非您使用反向调试,否则这些误报并不总是可行.
请看以下情况:
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",因为您的堆栈粉碎保护器处于活动状态,并且发现程序中存在堆栈溢出.
只需找出发生的位置,并修复它.
您可以尝试使用valgrind调试问题:
Valgrind发行版目前包括六种生产质量工具:内存错误检测器,两个线程错误检测器,一个缓存和分支预测分析器,一个调用图生成缓存分析器和一个堆分析器.它还包括两个实验工具: 堆/堆栈/全局数组溢出检测器和SimPoint基本块矢量生成器.它运行在以下平台上:X86/Linux,AMD64/Linux,PPC32/Linux,PPC64/Linux和X86/Darwin(Mac OS X).