PromleeBlog
sitemap
aboutMe

posting thumbnail
정보보안기사 - 1.6 버퍼 오버플로우(BOF) 원리와 메모리 보호 기법
Buffer Overflow Principles and Memory Protection Techniques - InfoSec Engineer 1.6

📅

들어가기 전에 🔗

지난 1.5편까지는 운영체제의 구조와 권한 관리에 대해 알아보았습니다.
이번 시간부터는 애플리케이션의 취약점을 파고드는 공격 기법을 다룹니다.
그중 가장 대표적이고 역사 깊은 공격이 바로
버퍼 오버플로우
(Buffer Overflow)입니다.

프로그램이 데이터를 저장하기 위해 준비한 공간(버퍼)보다 더 큰 데이터를 집어넣으면 어떻게 될까요?
넘쳐흐른 데이터가 시스템의 중요한 메모리 영역을 덮어써 버리게 됩니다.
이 현상을 이용해 해커가 어떻게 시스템 통제권을 가져가는지, 그리고 운영체제는 이를 어떻게 막아내는지 살펴보겠습니다.

프로세스 메모리 구조 🔗

공격을 이해하려면 먼저 공격 대상인
프로세스 메모리
가 어떻게 생겼는지 알아야 합니다.
프로그램이 실행되면 운영체제로부터 메모리 공간을 할당받는데, 이는 크게 4가지 영역으로 나뉩니다.

4가지 주요 영역 🔗

  1. 코드(Code) 영역
    실행할 기계어 명령(Instruction)들이 저장되는 곳입니다. 읽기 전용(Read-Only)입니다.
  2. 데이터(Data) 영역
    전역 변수(Global Variable)나 정적 변수(Static Variable)가 저장됩니다.
    프로그램 시작 시 할당되고 종료 시 소멸합니다.
  3. 힙(Heap) 영역
    프로그래머가 필요할 때 동적으로 할당하는 공간입니다(malloc 등).
    메모리의
    낮은 주소에서 높은 주소 방향
    으로 자라납니다.
  4. 스택(Stack) 영역
    함수 호출 시 지역 변수(Local Variable)와 매개변수 등이 임시로 저장되는 공간입니다.
    메모리의
    높은 주소에서 낮은 주소 방향
    으로 자라납니다.
[중요]
스택과 힙은 서로 마주 보고 자라나는 구조입니다.
스택이 너무 많이 자라나면
Stack Overflow
, 힙이 너무 많이 자라나면
Out of Memory
가 발생합니다.

Stack Buffer Overflow 공격 원리 🔗

가장 흔하게 발생하는 스택 기반의 오버플로우 공격 원리를 알아보겠습니다.
핵심은 함수가 종료될 때 돌아갈 주소인
RET
(Return Address)를 조작하는 것입니다.

스택 프레임(Stack Frame) 구조 🔗

함수가 호출되면 스택에는 다음과 같은 순서로 데이터가 쌓입니다. (높은 주소 -> 낮은 주소 순)
  1. RET (Return Address)
    : 함수 종료 후 돌아갈 코드의 주소.
  2. SFP (Saved Frame Pointer)
    : 이전 함수의 스택 기준 주소.
  3. BUF (Buffer)
    : 지역 변수(데이터 저장 공간).

공격 메커니즘 🔗

만약 BUF의 크기가 10바이트인데, 사용자가 100바이트의 데이터를 입력하면 어떻게 될까요?
  1. 입력 데이터가 BUF를 가득 채웁니다.
  2. 넘친 데이터가 SFP를 덮어씁니다.
  3. 계속 넘친 데이터가 결국
    RET
    까지 덮어씁니다.
  4. 공격자는 이
    RET
    자리에
    악성코드가 위치한 주소
    쉘코드
    (Shellcode)의 주소를 집어넣습니다.
  5. 함수가 종료(return)되는 순간, CPU는 조작된
    RET
    주소로 점프하여 악성코드를 실행하게 됩니다.

취약한 C 코드 예시 🔗

vulnerable.c
#include <stdio.h>
#include <string.h>
 
void vulnerable_function(char *str) {
    char buffer[50]; // 50바이트 공간 할당
    // [취약점] 입력받은 str의 길이를 검사하지 않고 복사함
    strcpy(buffer, str); 
}
 
int main(int argc, char *argv[]) {
    vulnerable_function(argv[1]); // argv[1]에 공격용 긴 문자열 전달 가능
    return 0;
}
위 코드에서 argv[1]로 100바이트 이상의 문자열을 넣으면 strcpy 함수는 경계 검사를 하지 않고 그대로 복사하여
RET
를 덮어쓰게 됩니다.

메모리 보호 기법 (세 가지 방어기법) 🔗

운영체제 개발자들도 가만히 있지는 않았습니다.
이러한 공격을 막기 위해 현대 운영체제(Windows, Linux 등)에는 강력한 메모리 보호 기법이 적용되어 있습니다.

1. ASLR (Address Space Layout Randomization) 🔗

2. DEP (Data Execution Prevention) / NX Bit 🔗

3. Stack Canary (스택 카나리아) 🔗

스택 카나리아 보호기법
스택 카나리아 보호기법

안전하지 않은 함수와 시큐어 코딩 🔗

버퍼 오버플로우는 결국 프로그래머의 실수에서 시작됩니다.
C언어 프로그래밍 시 입력값의 길이를 검사하지 않는 함수 사용을 피해야 합니다.

금지해야 할 함수 (Unsafe) 🔗

이 함수들은 버퍼의 크기를 고려하지 않고 데이터를 씁니다.

권장하는 함수 (Safe) 🔗

반드시
복사할 크기
(Size)를 지정할 수 있는 함수를 사용해야 합니다.

시큐어 코딩 예시 🔗

앞서 본 취약한 코드를 안전하게 수정해 보겠습니다.
secure.c
#include <stdio.h>
#include <string.h>
 
void secure_function(char *str) {
    char buffer[50];
    // [안전] buffer의 크기 - 1 만큼만 복사 (널 문자 고려)
    strncpy(buffer, str, sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0'; // 문자열 끝 보장
}

결론 🔗

이번 시간에는 시스템 해킹의 가장 기본이 되는 버퍼 오버플로우와 그 대응 방안을 알아보았습니다.
다음 시간에는
1.7 레이스 컨디션과 포맷 스트링 공격 원리
에 대해 알아봅니다.
파일 접근 타이밍을 노리는 공격과 printf 함수의 기능을 악용한 메모리 조작 기법을 깊이 있게 다뤄보겠습니다.

참고 🔗