簡體   English   中英

檢測到堆棧粉碎

[英]Stack smashing detected

我正在執行我的 a.out 文件。 執行后程序運行一段時間然后退出並顯示消息:

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

這可能是什么原因,我該如何糾正?

這里的Stack Smashing其實是gcc用來檢測緩沖區溢出錯誤的保護機制造成的。 例如在以下代碼段中:

#include <stdio.h>

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

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

編譯器(在本例中為 gcc)添加具有已知值的保護變量(稱為金絲雀)。 大小大於 10 的輸入字符串會導致此變量損壞,從而導致 SIGABRT 終止程序。

要獲得一些見解,您可以嘗試在編譯時使用選項-fno-stack-protector禁用 gcc 的這種保護。 在這種情況下,您將收到不同的錯誤,很可能是在您嘗試訪問非法內存位置時出現分段錯誤。 請注意,應始終為發布版本打開-fstack-protector ,因為它是一項安全功能。

您可以通過使用調試器運行程序來獲取有關溢出點的一些信息。 Valgrind 不能很好地處理與堆棧相關的錯誤,但就像調試器一樣,它可以幫助您確定崩潰的位置和原因。

帶有拆卸分析的最小復制示例

主文件

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;
}

GitHub 上游.

編譯並運行:

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

根據需要失敗:

*** stack smashing detected ***: terminated
Aborted (core dumped)

在 Ubuntu 20.04、GCC 10.2.0 上測試。

在 Ubuntu 16.04、GCC 6.4.0 上,我可以使用-fstack-protector而不是-fstack-protector-all復制,但是根據耿嘉文的評論,當我在 GCC 10.2.0 上進行測試時,它停止了。 man gcc澄清,正如選項名稱所建議的, -all版本更積極地添加檢查,因此可能會導致更大的性能損失:

-fstack-protector

發出額外的代碼來檢查緩沖區溢出,例如堆棧粉碎攻擊。 這是通過向具有易受攻擊對象的函數添加保護變量來完成的。 這包括調用“alloca”的函數,以及緩沖區大於或等於 8 字節的函數。 守衛在進入函數時初始化,然后在函數退出時檢查。 如果防護檢查失敗,則會打印錯誤消息並退出程序。 僅考慮實際分配在堆棧上的變量,優化掉的變量或分配在寄存器中的變量不計算在內。

-fstack-protector-all

與 -fstack-protector 類似,只是所有功能都受到保護。

拆卸

現在我們看看反匯編:

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 <myfunc>
    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 <main+0x5b>
  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設置它來隨機化金絲雀,它包含一個隨機值,解釋如下:

調試嘗試

從現在開始,我們修改代碼:

    myfunc(arr, len + 1);

改為:

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

變得更有趣。

然后,我們將嘗試查看是否可以使用一種比僅閱讀和理解整個源代碼更自動化的方法來查明罪魁禍首+ 1調用。

gcc -fsanitize=address啟用 Google 的地址清理程序 (ASan)

如果使用此標志重新編譯並運行程序,它會輸出:

#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 行。

其源代碼位於: https : //github.com/google/sanitizers但正如我們從示例中看到的那樣,它已經上傳到 GCC。

ASan 還可以檢測其他內存問題,例如內存泄漏: 如何在 C++ 代碼/項目中查找內存泄漏?

Valgrind SGCheck

正如其他人提到的,Valgrind 不擅長解決此類問題。

它確實有一個名為 SGCheck的實驗工具:

SGCheck 是一種用於查找堆棧和全局數組溢出的工具。 它的工作原理是使用啟發式方法,該方法源自對堆棧和全局數組訪問的可能形式的觀察。

所以當它沒有發現錯誤時我並沒有很驚訝:

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

錯誤消息顯然應該是這樣的: Valgrind missing error

廣交會

一個重要的觀察是,如果您通過 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=<optimized out>, 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", <incomplete sequence \355\216>, 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", <incomplete sequence \355\216>, len=5) at main.c:3
#1  0x00000000004005cc in main () at main.c:12

現在,這確實讓我們看到了正確的違規指令: len = 5i = 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 <stdio.h>
#include <string.h>

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 wepasssssssssssssssss”時應該會發生這種情況

所以為了回答你上面的問題,顯示消息“** stack smashing detection : xxx”是因為你的stack smashingprotector處於活動狀態,發現你的程序中存在堆棧溢出。

只需找出發生的位置,然后修復它。

您可以嘗試使用valgrind調試問題:

Valgrind 發行版目前包括六個生產質量工具:一個內存錯誤檢測器、兩個線程錯誤檢測器、一個緩存和分支預測分析器、一個調用圖生成緩存分析器和一個堆分析器。 它還包括兩個實驗工具:堆/堆棧/全局數組溢出檢測器和 SimPoint 基本塊向量生成器。 它在以下平台上運行:X86/Linux、AMD64/Linux、PPC32/Linux、PPC64/Linux 和 X86/Darwin (Mac OS X)。

這意味着您以非法方式寫入堆棧上的某些變量,很可能是Buffer overflow的結果。

這可能是什么原因,我該如何糾正?

一種情況是在以下示例中:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void swap ( char *a , char *b );
void revSTR ( char *const src );

int main ( void ){
    char arr[] = "A-B-C-D-E";

    revSTR( arr );
    printf("ARR = %s\n", arr );
}

void swap ( char *a , char *b ){
    char tmp = *a;
    *a = *b;
    *b = tmp;
}

void revSTR ( char *const src ){
    char *start = src;
    char *end   = start + ( strlen( src ) - 1 );

    while ( start < end ){
        swap( &( *start ) , &( *end ) );
        start++;
        end--;
    }
}

在此程序中,如果您使用以下內容調用reverse() ,您可以反轉字符串或字符串的一部分:

reverse( arr + 2 );

如果您決定像這樣傳遞數組的長度:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void swap ( char *a , char *b );
void revSTR ( char *const src, size_t len );

int main ( void ){
    char arr[] = "A-B-C-D-E";
    size_t len = strlen( arr );

    revSTR( arr, len );
    printf("ARR = %s\n", arr );
}

void swap ( char *a , char *b ){
    char tmp = *a;
    *a = *b;
    *b = tmp;
}

void revSTR ( char *const src, size_t len ){
    char *start = src;
    char *end   = start + ( len - 1 );

    while ( start < end ){
        swap( &( *start ) , &( *end ) );
        start++;
        end--;
    }
}

也能正常工作。

但是當你這樣做時:

revSTR( arr + 2, len );

你得到:

==7125== Command: ./program
==7125== 
ARR = A-
*** stack smashing detected ***: ./program terminated
==7125== 
==7125== Process terminating with default action of signal 6 (SIGABRT)
==7125==    at 0x4E6F428: raise (raise.c:54)
==7125==    by 0x4E71029: abort (abort.c:89)
==7125==    by 0x4EB17E9: __libc_message (libc_fatal.c:175)
==7125==    by 0x4F5311B: __fortify_fail (fortify_fail.c:37)
==7125==    by 0x4F530BF: __stack_chk_fail (stack_chk_fail.c:28)
==7125==    by 0x400637: main (program.c:14)

發生這種情況是因為在第一個代碼中,在revSTR()內部檢查了arr的長度,這很好,但是在您傳遞長度的第二個代碼中:

revSTR( arr + 2, len );

Length 現在比你說arr + 2時通過的實際長度更長。

strlen ( arr + 2 ) != strlen ( arr )長度。

堆棧損壞通常由緩沖區溢出引起。 您可以通過防御性編程來防御它們。

每當您訪問一個數組時,在它之前放置一個斷言以確保訪問不會越界。 例如:

assert(i + 1 < N);
assert(i < N);
a[i + 1] = a[i];

這讓您考慮數組邊界,也讓您考慮在可能的情況下添加測試來觸發它們。 如果這些斷言中的一些在正常使用期間可能失敗,請將它們轉換為常規if

我在使用 malloc() 為 struct * 分配一些內存時遇到了這個錯誤,在調試代碼之后,我終於使用了 free() 函數來釋放分配的內存,隨后錯誤消息消失了:)

堆棧粉碎的另一個來源是(不正確)使用vfork()而不是fork()

我剛剛調試了一個案例,其中子進程無法execve()目標可執行文件並返回錯誤代碼而不是調用_exit()

因為vfork()已經產生了那個孩子,它在實際上仍在父進程空間內執行時返回,不僅破壞了父進程的堆棧,而且導致“下游”代碼打印兩組不同的診斷信息。

vfork()更改為fork()解決了這兩個問題,將 child 的return語句更改為_exit()

但是由於子代碼在execve()調用之前調用了其他例程(在這種特殊情況下設置 uid/gid),它在技術上不滿足vfork()的要求,因此將其更改為使用fork()在這里是正確的。

(請注意,有問題的return語句實際上並不是這樣編碼的——而是調用了一個宏,該宏決定是_exit()還是基於全局變量return 。因此,子代碼並不是很明顯不符合vfork()使用。)

有關更多信息,請參閱:

fork()、vfork()、exec()和clone()的區別

我正在執行我的a.out文件。 執行后,程序將運行一段時間,然后退出並顯示以下消息:

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

可能是什么原因造成的,我該如何糾正?

我在編輯結構時遇到了這個問題,但沒有重新編譯使用該結構的庫。 在一些大項目中,我向結構中添加了新字段,這些字段后來從 lib_struct 中的 json 進行了解析,此庫后來用於小部件以顯示解析的內容。 我的 make 文件沒有涵蓋依賴項,因此在編輯結構后 lib 沒有重新編譯。 我的解決方案是重新編譯所有使用該結構的東西。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM