IT story

스택 스매싱 감지

hot-time 2020. 4. 28. 08:23
반응형

스택 스매싱 감지


내 a.out 파일을 실행 중입니다. 실행 후 프로그램이 얼마 동안 실행 된 후 메시지와 함께 종료됩니다.

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

이에 대한 가능한 이유는 무엇이며 어떻게 수정해야합니까?


여기서 스택 스매싱은 실제로 버퍼 오버플로 오류를 감지하기 위해 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는 스택 관련 오류와 함께 잘 작동하지 않지만 디버거와 마찬가지로 충돌의 위치와 원인을 정확히 찾아내는 데 도움이 될 수 있습니다.


분해 분석을 통한 최소 재생 예

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

GitHub의 상류 .

컴파일하고 실행하십시오.

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 <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 ++ 코드 / 프로젝트에서 메모리 누수를 찾는 방법은 무엇입니까?

발 그린 드 SGCheck

마찬가지로 다른 사람에 의해 언급 , Valgrind의는 이러한 문제를 해결하기에 좋지 않다.

SGCheck라는 실험 도구가 있습니다 .

SGCheck는 스택 및 글로벌 어레이의 오버런을 찾는 도구입니다. 스택 및 전역 배열 액세스 가능성에 대한 관찰에서 파생 된 휴리스틱 접근 방식을 사용하여 작동합니다.

그래서 오류를 찾지 못했을 때 놀라지 않았습니다.

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

오류 메시지는 다음과 같이 나타납니다. Valgrind missing error

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=<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 = 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 <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 wepassssssssssssssssss"

So to answer your question above, the message "** stack smashing detected : xxx" was displayed because your stack smashing protector was active and found that there is stack overflow in your program.

Just find out where that occurs, and fix it.


You could try to debug the problem using valgrind:

The Valgrind distribution currently includes six production-quality tools: a memory error detector, two thread error detectors, a cache and branch-prediction profiler, a call-graph generating cache profiler, and a heap profiler. It also includes two experimental tools: a heap/stack/global array overrun detector, and a SimPoint basic block vector generator. It runs on the following platforms: X86/Linux, AMD64/Linux, PPC32/Linux, PPC64/Linux, and X86/Darwin (Mac OS X).


It means that you wrote to some variables on the stack in an illegal way, most likely as the result of a Buffer overflow.


What could be the possible reasons for this and how do I rectify it?

One scenario would be in the following example:

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

In this program you can reverse a String or a part of the string if you for example call reverse() with something like this:

reverse( arr + 2 );

If you decide to pass the length of the array like this:

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

Works fine too.

But when you do this:

revSTR( arr + 2, len );

You get get:

==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)

And this happens because in the first code, the length of arr is checked inside of revSTR() which is fine, but in the second code where you pass the length:

revSTR( arr + 2, len );

the Length is now longer then the actually length you pass when you say arr + 2.

Length of strlen ( arr + 2 ) != strlen ( arr ).


Stack corruptions ususally caused by buffer overflows. You can defend against them by programming defensively.

Whenever you access an array, put an assert before it to ensure the access is not out of bounds. For example:

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

This makes you think about array bounds and also makes you think about adding tests to trigger them if possible. If some of these asserts can fail during normal use turn them into a regular if.


I got this error while using malloc() to allocate some memory to a struct * after spending some this debugging the code, I finally used free() function to free the allocated memory and subsequently the error message gone :)


Another source of stack smashing is (incorrect) use of vfork() instead of fork().

I just debugged a case of this, where the child process was unable to execve() the target executable and returned an error code rather than calling _exit().

Because vfork() had spawned that child, it returned while actually still executing within the parent's process space, not only corrupting the parent's stack, but causing two disparate sets of diagnostics to be printed by "downstream" code.

Changing vfork() to fork() fixed both problems, as did changing the child's return statement to _exit() instead.

But since the child code precedes the execve() call with calls to other routines (to set the uid/gid, in this particular case), it technically does not meet the requirements for vfork(), so changing it to use fork() is correct here.

(Note that the problematic return statement was not actually coded as such -- instead, a macro was invoked, and that macro decided whether to _exit() or return based on a global variable. So it wasn't immediately obvious that the child code was nonconforming for vfork() usage.)

For more information, see:

The difference between fork(), vfork(), exec() and clone()

참고URL : https://stackoverflow.com/questions/1345670/stack-smashing-detected

반응형