버퍼 오버플로우의 공격 경로와 메모리 보호 전략
버퍼 오버플로우가 복귀 주소와 제어 흐름을 훼손하는 방식, 로컬·원격 공격 차이, OS·운영·개발 단계의 대응 전략을 정리한다.
2026-08-14 · 최초 발행 2025-05-26
입력이 버퍼 경계를 넘을 때 벌어지는 일
Buffer Overflow는 프로그램이 할당받은 메모리 버퍼보다 많은 데이터를 기록하면서 인접 영역까지 덮어쓰는 메모리 손상 취약점이다. 단순한 오류로 끝나지 않고, 프로그램의 제어 흐름을 바꾸거나 시스템 권한을 탈취하는 경로가 될 수 있다.
공격은 대체로 취약한 서비스나 Root 권한으로 실행되는 프로그램을 대상으로 한다. 공격자는 프로그램이 예상한 범위를 초과하는 입력을 보내고, 이 데이터가 스택의 중요 정보, 특히 복귀 주소까지 침범하도록 유도한다. 이후 복귀 주소가 공격자가 지정한 코드의 위치를 가리키면 함수 반환 시점에 프로그램 흐름이 바뀌며 악성 쉘코드가 실행될 수 있다.
로컬 권한 상승과 원격 서비스 침해
Local Buffer Overflow는 시스템에 직접 접근할 수 있는 사용자가 로컬에서 실행되는 취약한 프로그램을 겨냥하는 방식이다. Root 권한으로 동작하는 프로그램이나 SUID(Set-User-ID) 비트가 설정된 프로그램이 주요 대상이 되며, 공격에 성공하면 일반 사용자가 Root 권한을 얻을 수 있다.
void vulnerable_function(char *input) {
char buffer[64];
strcpy(buffer, input); // 입력 길이 검사 없이 복사
}
이 코드에서 strcpy()는 입력 길이를 확인하지 않은 채 데이터를 복사한다. 64바이트보다 큰 입력이 들어오면 버퍼 오버플로우가 발생해 복귀 주소를 포함한 스택의 다른 영역이 덮어써질 수 있다.
Remote Buffer Overflow는 네트워크를 통해 취약한 서비스를 공격한다. RPC, FTP, HTTP 같은 네트워크 서비스가 대상이 될 수 있으며, 원격에서 접근할 수 있다는 점에서 영향 범위가 커질 수 있다. 성공하면 원격 시스템에 대한 접근권한을 획득할 가능성이 있다.
Morris 웜(1988)은 fingerd 프로그램의 버퍼 오버플로우 취약점을 이용한 최초의 인터넷 웜이다. Code Red 웜(2001)은 Microsoft IIS 웹 서버의 버퍼 오버플로우 취약점을 이용했고, Slammer 웜(2003)은 MS-SQL 서버의 버퍼 오버플로우 취약점을 이용해 초고속으로 전파됐다.
스택과 복귀 주소를 겨냥하는 흐름
함수가 호출되면 스택에는 지역 변수, 이전 프레임 포인터, 복귀 주소 같은 정보가 저장된다. x86 아키텍처에서는 스택이 높은 주소에서 낮은 주소 방향으로 성장한다.
공격 데이터는 버퍼를 넘겨 쉘코드, 패딩, 새로운 복귀 주소가 이어지도록 구성될 수 있다. 이때 복귀 주소는 쉘코드의 시작 주소를 가리키도록 조작되며, 함수가 끝날 때 프로세서는 조작된 주소로 제어를 넘긴다.
복귀 주소가 쉘코드를 가리키면 프로세서는 이를 일반 프로그램 코드처럼 실행한다. 쉘코드는 일반적으로 /bin/sh와 같은 명령 쉘을 실행하는 코드로 구성된다.
방어는 운영체제, 운영, 개발 단계에서 겹쳐야 한다
운영체제는 데이터 영역에서 코드 실행을 막는 DEP(Data Execution Prevention), 주소 공간을 무작위화하는 ASLR(Address Space Layout Randomization), 복귀 주소 앞의 값 변조를 확인하는 스택 카나리(Stack Canary) 같은 보호 기법을 제공한다. 함수 호출과 반환 시 스택 프레임 포인터의 무결성을 검증하는 스택 무결성 검사도 대응 수단이다.
시스템 관리자는 알려진 취약점을 제거하도록 보안 패치를 신속히 적용하고, 불필요한 SUID/SGID 프로그램을 제거하거나 제한해야 한다. SELinux, AppArmor 같은 강화된 보안 OS를 도입하고, 네트워크 서비스를 최소화하며 방화벽을 설정하는 조치도 필요하다. IDS/IPS 구축은 침입 탐지와 방지에 활용할 수 있다.
개발 단계에서는 모든 외부 입력의 길이와 형식을 검증해야 한다. strcpy() 대신 strncpy(), gets() 대신 fgets()처럼 경계 검사가 가능한 함수를 사용하고, 정적 분석 도구로 소스 코드의 취약점을 미리 찾는다. Java, Python, Rust처럼 메모리 안전성을 보장하는 언어를 고려하는 것도 한 방법이다.
// 안전하지 않은 코드
void unsafe(char *input) {
char buffer[64];
strcpy(buffer, input); // 버퍼 오버플로우 가능성
}
// 안전한 코드
void safe(char *input) {
char buffer[64];
strncpy(buffer, 63, input);
buffer[63] = '\0'; // NULL 종료 문자 보장
}
오래된 문제가 남아 있는 이유
Buffer Overflow는 발견된 지 수십 년이 지났지만, 오래된 코드베이스와 C/C++로 작성된 시스템이 계속 사용되면서 위험이 남아 있다. 제한된 자원을 가진 IoT 기기는 메모리 안전성을 희생하는 경우가 많아 또 다른 취약 지점이 된다.
힙 오버플로우와 형식 문자열 취약점처럼 메모리 손상과 연결되는 변형도 계속 발견된다. 알려지지 않은 Buffer Overflow 취약점은 고가의 제로데이 공격에 활용될 수 있다. 따라서 운영체제 보호 기능만으로 충분하다고 보기보다, 운영 통제와 안전한 코딩 관행을 함께 유지해야 한다.