Information Technology

버퍼 오버플로우 공격(Buffer Overflow Attack) 원리와 개념

coinAA 2021. 11. 22.

 

목차

     

     

    FullMoon System

     

    fullmoon-system.com

    버퍼 오버플로우 공격(Buffer Overflow Attack)의 개념

    • 기본적인 버퍼 오버플로우 공격은 데이터의 길이에 대한 불명확한 정의를 악용한 덮어쓰기로 발생한다.
    • 경계선 관리가 적절하게 수행되어 덮어쓸 수 없는 부분에 해커가 임의의 코드를 덮어쓰는 것을 의미한다.
      (복귀 주소 변경 -> 임의 코드 실행)
    • 버퍼 오버플로우에 취약한 함수와 그렇지 않은 함수가 있는데, 프로그래머가 취약한 특정 함수를 사용하지 않는다면 공격이 훨씬 어려워진다.

     

    버퍼 오버플로우 발생 원인

    버퍼 오버플로우 발생 원인에 대해서 이해하기 위해서는 먼저 프로그램이 데이터를 어떻게 저장하고, 함수가 어떻게 호출되는지를 이해해야 한다. 하나의 프로그램은 수 많은 함수로 구성되어있는데 프로그램을 실행하여 함수가 호출될 떄, 지역 변수와 복귀주소(Return Address)가 스택이라 하는 논리 데이터 구조에 저장된다.
    복귀주소가 저장되는 이유는 함수가 종료될 때 OS가 함수를 호출한 프로그램에게 제어권을 넘겨야 하는데, 이 복귀주소가 없으면 함수가 종료된 후 어떤 명령어를 수행해야 할지 알수 없기 때문이다.
    버퍼 오버플로우는 이 복귀주소가 함수가 사용하는 지역변수의 데이터에 의해 침범 당할 때 발생하게 된다. 프로그래머 실수로 지역변수가 할당된 크기보다 큰 크기를 입력해버리면 복귀주소가 입력한 데이터에 의해 다른 값으로 변경 되는데 함수가 종료될 때 전혀 관계없는 복귀주소를 실행시키게 되어서 실행 프로그램이 비정삭적으로 종료하게 된다.
    악의적인 공격자가 이러한 취약점을 알고서 데이터의 길이와 내용을 적절히 조정하여 버퍼 오버플로우를 일으켜서 특정 코드를 실행시키게 하는 해킹 공격 기법을 버퍼 오버플로우 공격이라고 한다.

     

    버퍼 오버플로우 종류

    • Stack Buffer Overflow
      • 스택(함수 처리를 위해 지역 및 매개변수가 위치하는 메모리 영역) 구조 상, 할당된 버퍼들이 정의된 버퍼 한계치를 넘는 경우, 복귀 주소를 변경하여 공격자가 임의 코드를 수행
    • Heap Buffer Overflow
      • 힙(malloc()등의 메모리 할당 함수로 사용자가 동적으로 할당하는 메모리 영역) 구조 상, 최초 정의된 힙의 메모리 사이즈를 초과하는 문자열들이 힙의 버퍼에 할당될 시, 공격자가 데이터 변경 및 함수 주소 변경으로 임의 코드를 수행

     

    버퍼 오버플로우 공격의 원리

    버퍼 오버플로우에 취약한 함수가 포함된 가장 기본적인 예제 코드

    int main(int argc, char *argv[])
    {
    	char buffer[10];
        strcpy(buffer, argv[1]);
        printf("%s\n", &buffer);
    }

     

    • int main(int argc, char *argv[ ])
      • argc는 취약한 코드인 bugfile.c가 컴파일되어 실행되는 프로그램의 인수 개수. *argv[ ]는 포인터 배열로서 인자로 입력되는 값의 번지수를 차례로 저장한다.
      • argv[0]: 실행 파일의 이름
      • argv[1]: 첫 번째 인자의 내용
      • argv[2]: 두 번째 인자의 내용
    • char buffer[10]
      • 10바이트 크기의 버퍼를 할당
    • strcpy(buffer, argv[1])
      • 버퍼에 첫 번째 인자(argv[1])를 복사
      • abcd 값을 버퍼에 저장(버퍼 오버플로우)
    • prinf(“%s\n”, &buffer)
      • 버퍼에 저장된 내용 출력

    버퍼 오버플로우 공격은 strcpy(buffer, argv[1]) 에서 일어나고 GDB(GNU 디버거)를 이용하여 main 함수를 살펴본 다음 strcpy가 호출되는 과정 살펴봐야한다.

     

    bugfile 컴파일과 GDB로 살펴본 bugfile의 main 함수

    gcc -o bugfile bugfile..c / gdb bugfile / disass main 명령 결과

    • 0x80483f8 〈main>
      • •         push %ebp
    • 0x80483f9 〈main+1>
      • mov %esp,%ebp
      • 스택에 EBP 값을 밀어 넣고 현재의 ESP 값을 EBP 레지스터에 저장한다.
    • 0x80483fb 〈main+3>
      • sub $0xc,%esp
      • main 함수의 char buffer[10];을 실행하는 과정
      • char 명령으로 메모리에 10바이트를 할당했으나 메모리에서는 모두 4바이트 단위로 할당되어 실제 할당되는 메모리는 12바이트이다.

    main + 3까지 실행 시 스택의 구조

    • 0x80483fe 〈main+6〉
      • mov 0xc(%ebp),%eax
      • EBP에서 상위 12바이트(0xC) 내용을 EAX 레지스터에 저장하면 EAX 레지스터는 RET보다 상위 주소의 값을 읽어 들인다.
      • 여기에는 [main + 6까지 실행 시 스택의 구조 그림]과 같이 char *argv[ ]를 가리키고 EAX에 argv[ ]에 대한 포인터 값이 저장된다.

      main + 6까지 실행 시 스택의 구조
       
    • 0x8048401 〈main+9〉
      • add $0x4,%eax
      • EAX 값을 4바이트만큼 증가, argv[ ]에 대한 포인터이므로 argv[1]을 가리킨다.
    • 0x8048404 〈main+12〉
      • mov (%eax),%edx
      • EAX 레지스터가 가리키는 주소 값을 EDX 레지스터에 저장, 프로그램 실행할 때 인수 부분을 가리킨다.
    • 0x8048406 〈main+14〉
      • push %edx
    • 프로그램을 실행할 때 인수에 대한 포인터를 스택에 저장, 인수를 넣지 않고 프로그램을 실행하면 0×0의 값이 스택에 저장된다.

    main + 14까지 실행 시 스택의 구조

     

    • 0x8048407 〈main+15〉
      • lea 0xfffffff4(%ebp),%eax
      • EAX 레지스터에 12(%ebp) 주소 값 저장한다.
    • 0x804840a 〈main+18〉
      • push %edx
      • 스택에 EAX 레지스터 값을 저장
      • lea(load effective address): 왼쪽 피연산자의 주소(메모리)를 오른쪽 피연산자(레지스터)로 전송함, 보통 C 언어에서 포인터 변수를 설정하는 데 사용된다.

    main + 18까지 실행 시 스택의 구조

    • 0x804840b 〈main+19〉
      • call 0x8048340 <strcpy>
      • 0x80483fe 〈main+6〉 ~ 0x804840a 〈main+18〉에서 strcpy(buffer, argv[1]);을 실행하기 위해 buffer, argv[1]과 관련된 사항을 스택에 모두 상주시키고 마지막으로 strcpy 명령 호출

     

    strcpy 함수는 입력된 인수의 경계를 체크하지 않으므로 10바이트 길이를 넘지 않아야만 그보다 큰 인수를 받아도 스택에 쓰이고 13개 A를 인수로 사용하면 A가 쌓일 것이다.

    A문자 13개 입력 시 저장된 EBP값 변조
    입력 버퍼 이상의 문자열을 입력할 때 발생하는 세그먼테이션 오류

    열여섯 번째 문자에서 세그먼테이션 오류(Segmentation fault) 발생된다.
    bugfile.c의 char buffer[10]이 할당되는 주소 공간이 12바이트, EBP가 저장되는 공간이 4바이트이므로 A가 16개, 즉 16바이트 (주소 공간 12바이트, EBP 저장 공간 4바이트)를 덮어씌우고 스택의 RET 값을 침범하면서 오류가 생긴 것이다.

    egg shell의 실행

    메모리의 0xbfffb58에 셸 적재
    일반 사용자 권한으로 돌아가서 펄(Perl)을 이용하여 A 문자열과 셸의 메모리 주소를 bugfile에 직접 실행한다.
    -e : 펄을 이용하여 프로그램을 실행할 때 쓰는 옵션으로 공격 시 셸의 주소를 1바이트씩 거꾸로 입력해줌, 이는 들어간 것은 거꾸로 나오는 스택 때문에 발생한다.

    스택 버퍼 오버플로우&nbsp; 공격의 수행

    공격 종료, 계정이 root로 바뀐 것을 확인할 수 있다.

    EBP 값을 지나 RET 값의 변조

     

    버퍼 오버플로우 공격의 대응책

    • 버퍼 오버플로우에 취약한 함수 사용하지 않기
      • 버퍼 오버플로우에 취약한 함수(입력값 검사)
        • strcpy(char *dest, const char *src);
        • strcat(char *dest, const char *src);
        • getwd(char *buf);
        • gets(char *s);
        • fscanf(FILE *stream, const char *format, …);
        • scanf(const char *format, …);
        • realpath(char *path, char resolved_path[ ]);
        • sprintf(char *str, const char *format);
      • 최신 운영체제 사용
        • 최신 운영체제에는 non-executable stack, 스택 가드(stack guard), 스택 실드(stack shield)와 같이 운영체제 내에서 해커의 공격 코드가 실행되지 않도록 하는 여러 가지 장치가 있다.
      • 컴파일 검사
        • 버퍼 오버플로우를 방지할 수 있는 고급 언어를 사용하거나 안전한 표준 라이브러리를 사용하여 코딩 표준을 따르거나, 스택 프레임(힙 영역에서 스택을 침범하지 못하게 함)의 손상을 탐지하는 기능을 탑재한다.

    댓글