정적코드분석/코딩표준

Buffer Overflow (버퍼 오버플로우) 란?

overflow

‘버퍼 오버플로우’는 프로그램이 메모리를 잘못 다뤄서 오류를 일으키거나 프로그램 보안 취약점을 야기시키는 결함입니다. 이 결함은 지난 수 십 년간 C/C++ 개발자를 괴롭혀온 역사와 전통이 있는(?) 오류이기 때문에 소프트웨어 보안 강의 항목에 필수적으로, 그리고 가장 먼저 배우는 항목 중 하나이기도 합니다. 최근에는 웹 어플리케이션 및 개발 언어의 발전에 따라 과거에 비해서 그 발생 빈도가 많이 떨어졌습니다. 웹 어플리케이션의 보안 취약성 중 상위 10개를 발표하는 OWASP Top 10 리스트에 버퍼 오버플로우는 2004년에 5위에 랭크되어 있지만 2007년 리스트에는 포함되지 않았고 2013년 리스트에도 포함되지 않았습니다. 최근 웹 어플리케이션이 많이 나오고 C와 C++가 예전만큼 많이 쓰이지 않지만 그 위협이 없어진 것은 아닙니다.

overflow
overflow(출처 : Flickr vshioshvili CC BY-SA 2.0)

역사속의 버퍼 오버플로우

버퍼 오버플로우는 1988년 모리스 웜(Morris Worm)을 통해서 공식적으로 컴퓨터 역사에 기록되었습니다. 모리스 웜은 위세는 대단했습니다. 당시 인터넷 호스트 6만개 중 10%인 6000대의 주요 유닉스 머신이 모리스 웜에 감염되었다 추산될 정도니까요. 미국 연방 회계 감사원의 자료에 따르면 당시 피해액이 천만 달러에서 1억 달러였다고 합니다. 이 모리스 웜은 당시 코넬(Cornell) 대학교 박사과정 1학년이었던 로버트 모리스(Robert Morris)가 인터넷의 크기를 알고자(물론 당사자의 말에 의하면) 만든 프로그램이었습니다. 웜의 특성 상 다른 컴퓨터로 전파시키는 기능이 가장 중요했는데, 이를 위해 모리스는 핑거데몬(fingerd)의 버퍼 오버플로우 결함을 이용하게 됩니다.

MIT 교수 Robert Morris 사진
MIT 서버에 웜을 퍼트리고서도 MIT 교수가 된 Robert Morris

하지만 본격적으로 버퍼 오버플로우를 이용한 공격이 유행한 때는 Aleph One이 1997년 Phrack이라는 온라인 보안 잡지에 “Smashing The Stack For Fun And Profit”라는 제목으로 버퍼 오버플로우(정확히는 스택 오버플로우. 추후 설명)에 대해서 다룬 글을 게재한 이후였습니다.

버퍼 오버플로우 살펴보기

간단한 예제코드로 설명을 해보겠습니다. 아래는 버퍼 오버플로우가 발생하는C 코드입니다.

#include <stdio.h>
int main(int argc, char* argv[])
{
char buffer[200];
strcpy(argv[0], buffer);
printf("Hello %s",buffer);
}

이 코드는 버퍼 오버플로우 취약성이 있는 코드입니다. 코드를 컴파일하고 실행시켰을 때 첫 번째 인자에 200자 이상의 문자를 입력하면 버퍼 오버플로우가 발생합니다. 이 경우 악의적인 공격자가 컴퓨터의 루트 권한을 탈취, 해당 컴퓨터를 자유자재로 다룰 수 있습니다. 왜 이런 일이 발생할까요?

버퍼 오버플로우는 어떻게 발생하는가?

버퍼 오버플로우를 이해하기 위해서는 먼저 프로그램이 데이터를 어떻게 저장하고 어떻게 함수가 호출되는지 알아야 합니다. 하나의 프로그램은 수 많은 함수로 구성되어 있는데 이 함수가 호출될 때, 지역 변수와 복귀 주소(Return Address)가 스택(Stack)이라 하는 논리 데이터 구조에 저장됩니다. 복귀 주소가 저장되는 이유는 함수가 종료될 때 운영체제가 함수를 호출한 프로그램에 제어권을 넘겨야 하는데, 이 복귀주소가 없으면 함수가 종료된 후 어떤 명령어를 수행해야 할 지 모르기 때문입니다.

buffer_overflow

버퍼 오버플로우는 이 복귀 주소가 함수가 사용하는 지역 변수의 데이터에 의해 침범 당할 때 발생합니다. 프로그래머 실수로 지역 변수가 할당된 크기보다 더 많은 데이터를 입력하면(위 그림 b) 복귀 주소가 이 데이터에 의해서 다른 값으로 변경이 되기 때문에 함수가 종료될 때 엉뚱한 복귀 주소를 실행시키게 되어서 어플리케이션이 뜻하지 않게 종료됩니다. 더 문제는 악의적인 공격자가 이 버퍼 오버플로우 결함을 이용하는 경우입니다. 보통 버퍼 오버플로우 공격(Buffer Overflow Attack)이라고 하는데, 공격자가 이 취약점을 알고 데이터의 길이와 내용을 적절히 조정해서 버퍼 오버플로우를 일으켜서 특정 코드를 실행시키게 하는 해킹 기법에 사용됩니다.

버퍼 오버플로우에는 위에 설명한 방식만 있는 것은 아닙니다. 지금까지 설명은 스택 기반 방식이며 이 방식 말고 힙(Heap)을 이용한 방식도 있습니다.

버퍼 오버플로우와 프로그래밍 언어 선택

버퍼 오버플로우는 C와 C++, 어셈블리 언어에서 발생합니다.? Java, Perl, Python과 같이 메모리를 자체적으로 관리하는 언어는 버퍼 오버플로우가 발생하지 않습니다. 또한 C#과 같은 언어는 오버플로우 보호 기능을 제공하기 때문에 안전합니다.

버퍼 오버플로우를 피하려면

버퍼 오버플로우를 막는 많은 기법들이 나왔지만, 완벽하게 피하는 방법은 아직까지 없습니다.

먼저 버퍼 오버플로우를 일으킬 여지가 있는 표준 라이브러리 함수를 사용하지 말아야 합니다. 버퍼의 경계 검사를 하지 않는 표준 C라이브러리의 strcpy(), strcat(), gets(), sprintf() 등이 이에 해당됩니다.

두 번째, 입력 값 검사를 해야 합니다. 모든 입력을 받아들이고 특정 입력을 차단하는 방식에서 특정 입력만 받아들이고 나머지를 차단하는 방식을 고려해야 합니다.

세 번째, 프로그래밍 시 버퍼 경계 검사를 해야 합니다.

네 번째, 버퍼 오버플로우에 안전한 라이브러리를 사용해야 합니다.

다섯 번 째, 각종 버퍼 오버플로우 방지 기법을 사용합니다.?Libsafe, StackGuard 등이 이에 해당됩니다.

여섯 번째, 버퍼 오버플로우를 정적 분석 도구를 사용해야 합니다. 많은 분석도구가 이 버퍼 오버플로우 결함을 탐지해낼 수 있습니다.

* 이 글은 파수닷컴 기업 블로그에도 게재된 글입니다.

참고자료

1. CWE-121: Stack-based Buffer Overflow?

Leave a Reply

Leave a Reply

Your email address will not be published. Required fields are marked *