요약

버퍼 오버플로는 프로그램이 정해진 크기의 칸(버퍼)에 그보다 긴 입력을 넣어 옆 칸까지 덮어쓰는 오류다. 스택에서는 함수가 끝나고 돌아갈 주소까지 덮을 수 있어, 공격자가 그 주소를 자기 코드로 바꿔 놓으면 프로그램을 빼앗는다. 막는 방법은 두 갈래다. 프로그램을 만들 때 애초에 넘치지 않게 하는 것(컴파일 시간 방어)과, 이미 있는 프로그램이 공격받아도 공격이 통하지 않게 메모리 관리를 바꾸는 것(실행 시간 방어)이다.

예시로 보기

C 함수 안에 8바이트 배열이 있고, 입력 길이를 확인하지 않고 복사한다1.

void greet(char *input) {
    char buf[8];
    strcpy(buf, input);   /* input 길이를 확인하지 않음 */
}

스택에는 대략 이렇게 놓인다(위가 높은 주소).

| 돌아갈 주소 (return address) |  ← 덮이면 함수가 끝날 때 공격자가 정한 곳으로 간다
| 이전 프레임 포인터          |
| buf[0..7]                  |  ← 여기서부터 위로 채워 나간다

입력이 20바이트면 buf 8바이트를 넘어 프레임 포인터와 돌아갈 주소까지 덮는다. 공격자가 입력 안에 기계어를 넣고 돌아갈 주소를 그 기계어의 위치로 바꾸면, 함수가 끝나는 순간 공격자의 코드가 실행된다.

정확히 말하면

스택 버퍼 오버플로에 대한 방어는 크게 둘이다.

컴파일 시간 방어

새 프로그램을 공격에 견디게 단단히 만든다.

방법 내용
프로그래밍 언어 선택 안전하지 않은 코딩을 허락하지 않는 언어(배열 경계를 검사하는 자바, 파이썬 등)를 쓴다
안전한 코딩과 감사 프로그래머가 코드를 검사해 안전하지 않은 구조를 안전하게 다시 쓴다. 대대적인 코드 감사를 하기도 한다
언어 확장과 안전한 라이브러리 컴파일러가 배열 참조에 범위 검사를 자동으로 넣게 하거나, strcpy 대신 길이를 받는 함수를 쓴다
스택 보호 기법 함수 진입 때 돌아갈 주소 앞에 임의의 값(카나리)을 두고, 함수가 끝날 때 그 값이 그대로인지 확인한다. 바뀌었으면 넘침으로 보고 프로그램을 멈춘다1

실행 시간 방어

프로세스 가상 주소 공간의 메모리 관리를 바꾼다. 메모리 영역의 속성을 바꾸거나, 노리는 버퍼의 위치를 예측하기 어렵게 만들어 많은 공격을 막는다.

방법 내용
실행 가능 주소 공간 보호 많은 공격이 기계어를 버퍼에 복사한 뒤 그리로 실행을 옮긴다. 스택(과 힙)의 코드 실행을 막으면 이 공격이 통하지 않는다. 페이지 표 항목에 실행 금지 비트를 두어 하드웨어로 막는다
주소 공간 무작위화 스택, 힙, 라이브러리의 위치를 실행할 때마다 무작위로 바꿔, 공격자가 돌아갈 주소에 넣을 위치를 알 수 없게 한다1
가드 페이지 중요한 메모리 영역 사이에 접근 금지 페이지를 끼워, 넘친 쓰기가 그 페이지에 닿으면 바로 오류가 나게 한다1

활용

  • 리눅스와 Windows는 실행 금지(NX, DEP), 주소 공간 무작위화(ASLR), 스택 카나리(gcc의 -fstack-protector)를 기본으로 켠다1.
  • 실행 금지 비트는 페이지 표 항목의 보호 비트 하나다. 가상 메모리 하드웨어가 보안에도 쓰인다.
  • Rust 같은 언어는 컴파일 시간에 메모리 안전성을 검사해 이 부류의 오류를 대부분 없앤다1.

연결

  • 선수: 악성 소프트웨어, 가상 메모리 (페이지 보호, 주소 공간)
  • 2-1학기 어셈블리어의 스택 프레임과 CALL/RET이 돌아갈 주소가 스택에 놓이는 이유다.

확인 문제

C1 스택 버퍼 오버플로가 단순한 프로그램 오류를 넘어 공격자가 코드를 실행하는 데까지 이어지는 이유는?

답: 스택에는 지역 버퍼 바로 위에 함수가 끝나고 돌아갈 주소가 있다. 버퍼를 넘친 입력이 이 주소를 덮으면, 함수가 끝날 때 프로세서는 공격자가 넣은 주소로 점프한다. 그 주소에 공격자의 기계어를 두면 그 코드가 실행된다.

C2 다음 방어는 컴파일 시간 방어인가 실행 시간 방어인가? ① 스택 영역을 실행 금지로 표시 ② strcpy를 strncpy로 바꿈 ③ 실행할 때마다 스택 시작 주소를 무작위로 ④ 함수 끝에서 카나리 값 확인

답: ① 실행 시간 ② 컴파일 시간(안전한 라이브러리) ③ 실행 시간 ④ 컴파일 시간(컴파일러가 확인 코드를 넣는 스택 보호).

C3 스택 실행 금지만으로는 모든 버퍼 오버플로 공격을 막지 못한다. 공격자가 돌아갈 주소에 무엇을 넣으면 우회할 수 있는가? 이를 막는 다른 방어는?

답: 스택의 자기 코드 대신, 이미 실행 가능한 영역에 있는 라이브러리 함수(예: 셸을 띄우는 함수)의 주소를 넣으면 된다. 실행 금지는 스택의 코드 실행만 막기 때문이다. 주소 공간 무작위화가 그 라이브러리 함수의 위치를 예측하기 어렵게 만들어 이를 막는다.

  1. Stallings 7판의 버퍼 오버플로 절(컴파일 시간·실행 시간 방어)을 바탕으로 보탰다. ↩ ↩2 ↩3 ↩4 ↩5 ↩6