카테고리 없음

버퍼 오버플로우

csi1201 2024. 9. 30. 21:48

버퍼란?

버퍼 또는 데이터 버퍼는 데이터를 한 장소에서 다른 장소로 이동하는 동안 임시로 저장하는 데 사용되는 물리적 메모리 저장 영역입니다. 이러한 버퍼는 일반적으로 RAM 메모리에 저장됩니다. 컴퓨터는 성능 향상을 위해 버퍼를 자주 사용하고, 대부분의 최신 하드 드라이브는 데이터에 효율적으로 액세스하기 위해 버퍼링을 활용하며, 많은 온라인 서비스에서도 버퍼를 사용합니다. 예를 들어 버퍼는 온라인 동영상 스트리밍에서 끊김을 방지하기 위해 자주 사용됩니다. 동영상이 스트리밍되면 동영상 플레이어는 한 번에 약 20%의 동영상을 다운로드하여 버퍼에 저장한 다음 해당 버퍼에서 스트리밍합니다. 이렇게 하면 연결 속도가 약간 떨어지거나 빠른 서비스 중단이 발생해도 동영상 스트리밍 성능에는 영향이 미치지 않습니다.

버퍼는 특정 양의 데이터를 담도록 설계되었습니다. 버퍼에 너무 많은 데이터가 전송되면 데이터를 삭제하는 명령이 버퍼를 사용하는 프로그램에 내장되어 있지 않은 한, 프로그램은 버퍼에 인접한 메모리에 있는 데이터를 덮어씁니다.

공격자는 버퍼 오버플로를 악용하여 소프트웨어를 손상시킬 수 있습니다. 버퍼 오버플로 공격은 잘 알려져 있음에도 불구하고 여전히 사이버 보안 팀을 괴롭히는 주요 보안 문제입니다. 2014년에는 'Heartbleed'로 알려진 위협 때문에 수억 명의 사용자가 SSL 소프트웨어의 버퍼 오버플로 취약점으로 인해 공격에 노출되었습니다.

 

 

버퍼 오버플로란?

버퍼 오버플로는 버퍼에 데이터를 쓰는 소프트웨어가 버퍼의 용량을 초과하여 인접한 메모리 위치를 덮어쓸 때 발생하는 비정상적인 현상입니다. 달리 말하자면, 공간이 충분하지 않은 컨테이너에 너무 많은 정보가 전달되어 결국 해당 정보가 인접한 컨테이너의 데이터를 대체하게 되는 것입니다.

버퍼 오버플로는 프로그램 실행을 방해하거나 제어하기 위해 컴퓨터의 메모리를 수정하려는 공격자가 악용할 수 있습니다.

 

 

메모리 구조

코드 영역

메모리의 코드(code) 영역은 실행할 프로그램의 코드가 저장되는 영역으로 텍스트(code) 영역이라고도 부릅니다.

CPU는 코드 영역에 저장된 명령어를 하나씩 가져가서 처리하게 됩니다.

 

데이터 영역

메모리의 데이터(data) 영역은 프로그램의 전역 변수와 정적(static) 변수가 저장되는 영역입니다.

데이터 영역은 프로그램의 시작과 함께 할당되며, 프로그램이 종료되면 소멸합니다.

 

스택 영역

메모리의 스택(stack) 영역은 함수의 호출과 관계되는 지역 변수와 매개변수가 저장되는 영역입니다.

스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸합니다.

이렇게 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 합니다.

 

힙 영역

메모리의 힙(heap) 영역은 사용자가 직접 관리할 수 있는 '그리고 해야만 하는' 메모리 영역입니다.

힙 영역은 사용자에 의해 메모리 공간이 동적으로 할당되고 해제됩니다.

힙 영역은 메모리의 낮은 주소에서 높은 주소의 방향으로 할당됩니다.

 

 

버퍼 오버플로 공격

버퍼 오버플로(Buffer Overflow) 공격은 소프트웨어 보안에서 가장 오래되고 치명적인 취약점 중 하나입니다. 이 공격은 프로그램이 할당된 메모리 공간(버퍼)보다 많은 데이터를 처리하려고 할 때 발생하는데, 이를 악용하면 공격자는 시스템의 제어권을 장악할 수 있습니다.

 

1. 스택 기반 버퍼 오버플로 (Stack-Based Buffer Overflow)

스택(stack)은 함수가 호출될 때 로컬 변수를 저장하는 메모리 영역입니다. 스택 기반 버퍼 오버플로는 버퍼 크기보다 더 많은 데이터를 스택에 쓰는 과정에서 발생하는데, 이로 인해 함수의 반환 주소를 덮어쓰게 됩니다.

예를 들어, 함수가 gets()와 같은 입력 함수를 통해 데이터를 받을 때 길이 제한 없이 사용자가 긴 데이터를 입력하면, 스택에 저장된 반환 주소를 덮어씌울 수 있습니다. 공격자는 이 반환 주소를 조작하여 악성 코드를 실행할 수 있습니다.

  • 대응 방법: 버퍼 크기 제한을 준수하고, 안전한 입력 함수(예: fgets())를 사용해야 합니다.

fgets()

1. 사용법: fgets(char *str, int n, FILE *stream)

str: 입력받은 문자열을 저장할 버퍼

n: 입력받을 최대 문자 수(null문자 포함)

stream: 입력을 받을 스트림(일반적으로 stdin)

 

2. 주요 기능

  • 버퍼 오버플로 방지: 최대 입력 길이를 지정해 버퍼 크기 이상으로 데이터를 입력받지 않도록 합니다.
  • 개행 문자 포함: 입력된 문자열 끝에 개행 문자(\n)가 포함될 수 있습니다.

 

2. 힙 기반 버퍼 오버플로 (Heap-Based Buffer Overflow)

힙(heap)은 프로그램이 실행 중 동적으로 할당하는 메모리 영역입니다. 힙 기반 버퍼 오버플로는 할당된 메모리보다 큰 데이터를 저장하는 경우 발생하며, 공격자는 힙에 저장된 중요한 데이터를 덮어쓰거나 조작할 수 있습니다.

이 공격의 목표는 메모리 구조를 조작해 임의의 코드가 실행되도록 하는 것입니다. 예를 들어, 함수 포인터를 덮어쓰고 이를 악성 코드로 설정하는 방식입니다.

  • 대응 방법: 메모리 관리 크기 검증을 철저히 하고, 동적 메모리 할당 시 검증 루틴을 강화해야 합니다.

 

3. 정수 오버플로 (Integer Overflow)

정수 오버플로는 프로그램이 정수값을 처리하는 과정에서 발생하는 취약점입니다. 공격자는 매우 큰 정수값을 입력하여 메모리 할당 크기를 의도적으로 잘못 계산하게 만듭니다. 그 결과, 할당된 메모리보다 더 큰 데이터를 쓰게 되어 버퍼 오버플로를 유발할 수 있습니다.

  • 대응 방법: 정수값에 대한 범위 검증을 철저히 하고, 모든 값이 올바르게 처리되도록 주의해야 합니다.

 

4. 포맷 스트링 공격 (Format String Attack)

포맷 스트링 공격은 프로그램이 printf()와 같은 함수에 입력된 데이터를 출력할 때, 적절한 형식 지정자 없이 문자열을 처리할 때 발생합니다. 공격자는 이를 통해 메모리에 접근하거나 시스템의 중요한 데이터를 노출시킬 수 있습니다.

예를 들어, %x 형식을 이용해 메모리의 내용을 출력하거나, %n 형식을 사용해 메모리 값을 변경하는 식으로 공격할 수 있습니다.

  • 대응 방법: 포맷 문자열을 명시적으로 지정하고, 입력된 데이터에 대한 유효성을 검증하는 것이 중요합니다.

 

5. Return-to-libc 공격

버퍼 오버플로를 이용해 직접 악성 코드를 삽입하는 것이 어려울 때 사용하는 기법이 Return-to-libc 공격입니다. 이 기법은 시스템의 libc 라이브러리 함수(예: system())를 호출하여 악의적인 작업을 수행하게 만듭니다.

이 공격은 스택 기반 버퍼 오버플로를 이용해 함수의 반환 주소를 system() 함수의 주소로 변경한 후, 공격자가 원하는 명령어를 전달하는 방식으로 진행됩니다.

  • 대응 방법: ASLR(Address Space Layout Randomization)와 같은 기술을 통해 라이브러리 함수의 주소를 무작위로 배치하는 것이 효과적입니다.

ASLR(Address Space Layout Randomization)이란 메모리 손산 취약점 공격을 방지 하기 위한 기술입니다.

스택, 힙, 라이브러리 등의 주소를 랜덤한 영역에 배치하여, 공격에 필요한 Target address를 예측하기 어렵게 만듭니다.

프로그램이 실행 될 때마다 각 주소들이 변셩됩니다.

 

6. NOP 슬라이드 (NOP Sledding)

NOP 슬라이드는 버퍼 오버플로 공격의 성공 가능성을 높이기 위한 방법입니다. 공격자는 NOP(no operation) 명령어를 대량으로 삽입한 후 그 뒤에 악성 코드를 배치합니다. 프로그램이 오버플로로 인해 정확히 어디로 점프할지 알 수 없을 때, NOP 슬라이드를 통해 악성 코드로 안전하게 점프하도록 합니다.

  • 대응 방법: 데이터 실행 방지(DEP) 기능을 활성화하여 데이터를 포함한 메모리에서 코드를 실행하지 못하게 해야 합니다.

데이터 실행 방지(DEP)는Windows에 기본 제공되는 기술로, 실행 코드가 시작되지 않은 위치에서 실행 코드가 시작되지 않도록 보호합니다. DEP는 PC 메모리의 일부 영역을 데이터 전용으로 표시하여 해당 메모리 영역에서 실행 코드 또는 앱을 실행할 수 없습니다.

이는 버퍼 오버플로 또는 기타 기술을 사용하려는 공격이 일반적으로 데이터만 포함하는 메모리 부분에서 맬웨어를 실행하기 어렵게 하기 위해 설계되었습니다.

 

 

버퍼 오버플로 공격 대응 방법 정리

1. 입력 크기 검증

모든 입력 데이터를 처리할 때 그 길이를 철저하게 검증하고, 예상 범위를 넘는 입력을 차단합니다.

 

2.  스택 보호 기법(Stack Canaries)

스택에 보호 변수를 삽입하여, 스택에 불법적인 접근이 발생할 경우 이를 탐지하고 차단합니다.

 

3. ASLR(Address Space Layout Randomization)

메모리 주소를 무작위로 배치하여 공격자가 정확한 메모리 주소를 예측할 수 없도록 합니다.

 

4.  데이터 실행 방지(DEP)

코드가 실행되는 메모리 영역을 명확히 구분하여, 데이터 영역에서는 코드가 실행되지 않도록 설정합니다.

 

5. 안전한 코드 작성

메모리 관리를 엄격하게 하고, C/C++처럼 메모리 관리가 필요한 언어에서는 문자열 처리 시 충분한 버퍼를 확보합니다.

 

6. 취약점 점검 도구 사용

다양한 버퍼 오버플로 탐지 도구를 사용해 코드를 사전에 검사합니다.

 

7. 패치 및 업데이트

운영체제 및 소프트웨어의 최신 보안 패치를 항상 유지합니다.

 

 

버퍼 오버플로 해킹 사례

1. Morris Worm (1988년)

초기의 버퍼 오버플로 공격 사례 중 하나는 바로 "모리스 웜(Morris Worm)"입니다. 당시 MIT 학생이었던 로버트 모리스는 인터넷상에서 자신이 만든 웜을 퍼트렸습니다. 이 웜은 버퍼 오버플로 취약점을 악용해 시스템을 감염시키며, 네트워크 상의 많은 컴퓨터에 손상을 입혔습니다. 이 공격은 최초의 대규모 사이버 공격 사례로 기록되어 있으며, 모리스는 나중에 법적으로 책임을 지게 되었습니다.

 

2. SQL Slammer (2003년)

또 다른 대표적인 사례는 SQL Slammer 웜입니다. 이 웜은 마이크로소프트 SQL 서버의 버퍼 오버플로 취약점을 악용해 서버를 다운시키고, 전 세계적으로 수많은 서버에 영향을 끼쳤습니다. 공격은 매우 빠르게 퍼졌으며, 인터넷 속도 저하와 함께 많은 피해를 주었습니다.

 

3. Heartbleed (2014년)

Heartbleed는 OpenSSL 라이브러리의 버퍼 오버플로 취약점을 이용한 공격입니다. 이 취약점은 시스템 메모리의 데이터를 외부로 유출할 수 있었기 때문에, 공격자는 사용자 비밀번호, 신용카드 정보 등 민감한 데이터를 훔칠 수 있었습니다. 이 사건은 대규모 보안 패치와 함께 많은 기업들의 데이터 보호 인식을 높이는 계기가 되었습니다.

 

 

 

 

마지막으로 버퍼 오버플로는 컴퓨터 역사상 가장 오래된 보안 취약점 중 하나이지만, 그 심각성과 피해 규모는 여전히 매우 큽니다. 개발자와 시스템 관리자는 이를 예방하기 위한 노력을 게을리하지 말아야 하며, 항상 최신 보안 업데이트를 적용하고 코드를 점검하는 습관을 길러야 합니다.