힙 버퍼 오버플로우 공격은 프로그램이 할당받은 메모리 공간(버퍼)의 크기를 초과하는 데이터를 기록하여, 인접한 메모리 영역의 데이터를 덮어쓰는 보안 취약점 공격입니다. 이 공격은 소프트웨어 설계 과정에서의 오류를 악용하며, 시스템의 안정성과 데이터 무결성을 심각하게 위협합니다.
일반 사용자가 이 개념을 이해하는 가장 중요한 핵심은, 이 취약점이 하드웨어 문제가 아니라 소프트웨어의 메모리 관리 로직 오류에서 비롯된다는 점입니다. 공격자는 이 구조적 약점을 파고들어 시스템의 실행 흐름 자체를 조작하는 것을 목표로 합니다.
힙 버퍼 오버플로우 공격의 기본 메커니즘 이해
버퍼 오버플로우 공격을 정확히 이해하려면, 컴퓨터 메모리가 어떻게 구조화되어 메모리 공간이 관리되는지 알아야 합니다. 메모리는 크게 스택(Stack)과 힙(Heap) 영역으로 나뉘며, 이 둘은 목적과 관리 방식이 다릅니다.
1. 메모리 구조의 이해
- 스택(Stack): 주로 함수 호출 시 사용되는 지역 변수나 함수의 반환 주소 등, 정해진 순서대로 데이터가 쌓이고 해제되는 영역입니다.
- 힙(Heap): 프로그램 실행 중에 동적으로 메모리를 할당하고 해제하는 영역입니다. 운영체제나 라이브러리가 필요에 따라 메모리 블록을 요청하고 반환받는 곳입니다.
2. 힙 오버플로우 작동 원리
공격자가 악용하는 힙 오버플로우는 바로 이 힙 영역의 특성을 이용합니다. 예를 들어, 프로그램이 특정 데이터 처리를 위해 100바이트 크기의 버퍼를 힙에 할당받았다고 가정해 봅시다. 만약 공격자가 이 버퍼에 200바이트 분량의 악성 데이터를 주입한다면, 초과되는 100바이트가 인접한 메모리 영역(다른 변수나 포인터가 저장된 공간)을 덮어쓰게 됩니다. 이 메모리 덮어쓰기(Overwriting) 행위를 통해 공격자는 프로그램의 정상적인 흐름을 탈취하거나 원하는 코드를 실행시킬 수 있습니다.
스택 vs. 힙: 공격 지점의 차이점
공격자들이 목표로 삼는 메모리 영역에 따라 공격 방식이 달라집니다.
| 구분 | 스택 오버플로우 (Stack Overflow) | 힙 오버플로우 (Heap Overflow) |
| :— | :— | :— |
| 저장 대상 | 함수 호출 시의 지역 변수, 함수 반환 주소 등 | 동적으로 할당되는 데이터(자료구조, 큰 객체) |
| 공격 목표 | 함수가 돌아갈 다음 주소(Return Address)를 덮어써서 공격 코드 실행 | 인접한 데이터 구조체나 포인터를 덮어써서 프로그램 제어권 탈취 |
| 특징 | 비교적 예측하기 쉬워 공격에 많이 이용됨 | 복잡한 메모리 관리 구조를 공격해야 하므로 난이도가 높음 |
현대적 방어 기법과 취약점 방어 원리
운영체제와 컴파일러는 이러한 메모리 기반 공격에 대응하기 위해 여러 방어 메커니즘을 도입했습니다.
- ASLR (Address Space Layout Randomization): 프로그램이 메모리에 로드될 때마다 주소 공간의 배치를 무작위로 변경하여, 공격자가 공격 코드가 위치할 정확한 주소를 예측하기 어렵게 만듭니다.
- DEP/NX (Data Execution Prevention/No-Execute): 메모리 영역을 ‘데이터만 저장 가능한 영역’과 ‘코드만 실행 가능한 영역’으로 분리합니다. 공격자가 데이터를 저장한 메모리 영역에 코드를 심더라도, CPU가 해당 영역을 실행하지 못하도록 차단합니다.
취약점 방어 및 예방을 위한 개발 가이드
가장 효과적인 방어는 애초에 취약점을 만들지 않는 것입니다. 개발 단계에서 다음과 같은 사항을 준수해야 합니다.
- 입력값 검증 (Input Validation): 사용자로부터 받은 모든 입력값(데이터 길이, 형식 등)은 반드시 길이 제한 및 데이터 형식을 검사해야 합니다. (버퍼 오버플로우 방지 핵심)
- 안전한 API 사용: C/C++에서
strcpy,gets와 같이 버퍼 크기 검사를 수행하지 않는 함수 대신, 크기 제한을 명시적으로 제공하는 안전한 함수(예:strncpy_s)를 사용해야 합니다. - 최신 컴파일러 및 라이브러리 사용: 최신 컴파일러는 메모리 안전성을 높이는 기본 방어 장치를 포함하고 있습니다.
요약: 메모리 오버플로우 공격은 데이터를 저장하는 영역을 넘어 인접한 메모리 영역을 덮어써서 프로그램의 실행 흐름을 조작하는 행위입니다. 방어는 입력값의 엄격한 검증과 메모리 안전 코딩 습관을 통해 이루어져야 합니다.