버퍼 오버플로 보호
버퍼 오버플로 보호는 소프트웨어 개발 동안 스택에 할당된 변수들에 대한 버퍼 오버플로를 탐지하고 심각한 보안 취약점으로 사용되는 것을 막음으로써 실행 파일의 보안을 강화시키는 다양한 기법들을 가리킨다. 스택 버퍼 오버플로는 프로그램이 일반적으로 고정된 길이의 버퍼인 자신의 의도된 데이터 구조의 콜 스택 외부 메모리 주소에 쓸 때 발생한다. 스택 버퍼 오버플로 버그들은 프로그램이 할당된 버퍼에 실제로 할당된 것보다 더 많은 데이터를 쓸 때 발생한다. 이것은 거의 항상 스택의 인접한 데이터에 대한 오염을 유발하며 프로그램의 충돌이나 부정확한 동작 또는 보안 문제로 이어질 수 있다.
일반적으로 버퍼 오버플로 보호는 스택에 할당된 데이터를 체계화해서 카나리 값을 포함하는데, 이것은 스택 버퍼 오버플로에 의해 파괴될 시에 메모리에서 오버플로우 되기 전의 버퍼를 보여준다. 카나리 값을 검증함으로써 감염된 프로그램의 실행은 종료될 수 있고, 공격자가 제어를 넘겨받는 것으로부터 보호해 준다. 다른 버퍼 오버플로 보호 기법으로 경계 검사가 있는데 이것은 할당된 메모리의 각 블록에 대한 접근을 검사해서 실제로 할당된 공간을 넘지 못하게 한다. 태깅은 데이터를 저장하는 용도로 할당된 메모리가 실행 코드를 포함하지 못하게 보장한다.
스택에 할당된 버퍼를 넘치게 하는 것은 힙의 버퍼를 넘치게하는 것 보다 프로그램 실행에 영향을 줄 수 있는데, 이것은 스택이 모든 함수 호출들에 대한 반호나 주소를 갖기 때문이다. 그러나 비슷한 구현에 명시적인 보호들 또한 힙 기반 오버플로에 대해 존재한다.
GNU 컴파일러 모음, LLVM, 마이크로소프트 비주얼 스튜디오 등에는 버퍼 오버플로 보호의 여러 기법들이 존재한다.
스택 버퍼 오버플로는 스택 스매싱으로 알려진 공격의 한 부분으로서 고의적으로 유발될 수 있다. 만약 감염된 프로그램이 특별한 권한으로 실행되거나 신뢰되지 않은 네트워크 호스트로부터 데이터를 받는다면, 이 버그는 공격자가 실행 가능한 코드를 실행 중인 프로그램에 삽입하여 프로세스의 제어를 가질 수 있게 함으로써 잠재적으로 보안 취약점이 될 수 있다. 이것은 공격자가 컴퓨터에 대한 비인가된 접근을 갖게 하는 가장 오래된 방식 중 하나이다.
카나리 기법은 공격의 전체 계층을 막을 수 있다는 장점을 제공한다. 몇몇 연구에 따르면 이 기법의 성능에 대한 영향은 무시해도 될 정도라고 한다.
스택 스매싱 보호는 특정한 형태의 공격에 대한 방어를 할 수 없다. 예를 들면 이것은 힙에서의 버퍼 오버플로를 방어할 수 없다. 데이터 구조 내에서 데이터의 배치를 바꾸는 제대로 된 방식은 없다; 데이터 구조는 모듈 간에 같다는 것이 요구되며 특히 공유 라이브러리에서 그렇다. 구조 안의 어떤 데이터도 카나리들로 보호할 수 없다; 그래서 프로그래머들은 반드시 자신의 구조체를 사용해서 변수들을 어떻게 체계화할지를 신경써야 한다.
"카나리스(Canaries)" 또는 카나리("canary)"는 버퍼 오버플로를 감시하기 위해 스택의 버퍼와 제어 데이터 사이에 위치한 값들이다. 버퍼가 오버플로하면 오염될 첫 데이터는 보통 카나리일 것이고, 카나리 값의 검증은 실패하여 오버플로에 대한 경고가 발생하며 이후 처리될 수 있다.
카나리는 쿠키("cookies")라는 단어로 사용될 수 있다.
카나리로는 세 종류가 사용된다: "terminator", "random", 그리고 "random XOR". 스택가드(StackGuard)의 최신 버전은 세 가지 모두를 지원하며, ProPolice는 "terminator"와 "random" 카나리를 지원한다.
"Terminator 카나리"는 대부분의 버퍼 오버플로 공격들이 문자열 종결자로 끝나는 특정한 문자열 연산에 기반한다는 것에 대한 감시를 사용한다. 이 감시에 대한 반응은 카나리가 null 종결자, CR, LF, 그리고 -1이라는 것이다. 결과적으로 공격자는 카나리를 바꾸는 것을 피하기 위해 반환 주소를 쓰기 전에 널 null 문자를 써야 한다. 이것은 codice_1 를 사용하는 공격과 null 문자를 복사하며 반환하는 기법들을 막는다. 이 보호에도 불구하고 공격자는 잠재적으로 카나리를 알려진 값으로 겹쳐쓰고 정보를 틀린 값들로 제어해서 카나리 검사 코드를 통과할 수 있다.
"Random 카나리"는 공격자가 값을 아는 것을 막기 위해 (일반적으로 EGD에서) 랜덤하게 생성된 것이다. 일반적으로 익스플로잇을 위해 카나리를 읽는 것은 논리적으로 불가능하다; 카나리는 버퍼 오버플로 보호 코드 같이 오직 알아야 되는 이만 아는 보안 값이다.
일반적으로 랜덤 카나리는 프로그램 초기화 시에 생성되며 전역 변수로 저장된다. 이 값은 보통 매핑되지 않은 페이지에 패드돼서 이것을 읽으려는 시도는 세그먼테이션 폴트를 유발하고 프로그램을 종료시킨다. 공격자가 카나리가 어디에 있는지 위치를 안다면, 카나리를 읽거나 스택에서 읽는 프로그램을 구하는 것은 아직도 가능할 수 있다.
"Random XOR 카나리"는 제어 데이터의 모든 또는 부분을 사용해서 XOR을 섞은 랜덤 카나리이다. 이 방식에서는 카나리나 제어 데이터가 오염될 때 카나리 값이 달라진다.
랜덤 XOR 카나리스는 카나리를 얻기위한 "스택에서 읽기" 방식이 더 복잡해진 것을 제외하고 랜덤 카나리스와 같은 취약점들을 갖는다. 공격자는 반드시 카나리, 알고리즘을 얻어야 하고 원본 카나리를 생성하기 위해 데이터를 제어해야 한다.
게다가 랜덤 XOR 카나리스는 제어 데이터를 가리키게 바꾸는, 버퍼를 오버플로우 시키는 것과 관련된 특정한 종류의 공격을 방어할 수 있다. 만약 제어 데이터나 반환 값이 변하면 XOR 인코딩 때문에 카나리는 달라지게 된다. 이 포인터로 인해 제어 데이터나 반환 값은 카나리를 오버플로우하지 않고 바뀔 수 있다.
비록 이러한 카나리스가 클로버 된 포인터들을 통해 제어 데이터를 변경시키는 것으로부터 방어할 수 있지만, 다른 데이터나 포인터 자체는 방어할 수 없다. 특히 함수 포인터들은 여기서 문제가 되는데, 오버플로우될 수 있고 호출될 시에 셸코드를 실행할 수 있게 된다.
경계 검사는 할당된 메모리의 각 블록을 위한 런타임 경계 정보를 추가하고 런타임 시에 관련된 모든 포인터를 검사하는 컴파일러 기반 기법이다. C와 C++에서 경계 검사는 포인터 계산 시간 또는 역참조 시간에 수행될 수 있다.
이 접근법의 구현은 메모리의 할당된 각 블록을 서술하는 중앙 저장소, 또는 포인터와 추가적인 데이터(가리키는 영역을 서술하는)를 모두 포함하는 팻 포인터를 사용한다.
태깅은 메모리에서 데이터의 타입(주로 타입 검사 시에 사용되는)을 태그하기 위한 컴파일러 또는 하드웨어 기반 기법(태그 아키텍처를 요구하는)이다. 메모리의 특정한 영역을 실행 불가능으로 마킹함으로써 이것은 데이터를 저장하기 위해 할당된 메모리가 실행 가능한 코드를 포함하는 것으로부터 효과적으로 보호할 수 있다. 게다가 메모리의 특정한 영역들은 버퍼 오버플로우를 방어하기 위해 할당 불가능하게 마킹될 수 있다.
역사적으로 태깅은 고 수준 프로그래밍 언어들을 구현하는데 사용되어 왔다; 운영체제로부터의 적절한 지원 하에, 태깅은 버퍼 오버플로우를 탐지하는 용도로도 사용될 수 있다. 한 예시로 NX 비트 하드웨어가 있으며 이것은 인텔, AMD and ARM 프로세서들에서 지원된다.
스택 스매싱 방어는 1997년 StackGuard에 의해 처음 구현되었고 1998년 USENIX 보안 심포지엄에서 발표되었다. 스택가드는 GCC 2.7의 인텔 x86 백엔드를 위한 패치로서 도입되었다. 스택가드는 1998년부터 2003년까지 Immunix 리눅스 배포판을 위해 유지되었고 종결자, 랜덤 그리고 랜덤 XOR 카나리스를 구현하는 방식으로 확장되었다.
2001년부터 2005년까지, IBM은 "ProPolice"라고 알려진 스택 스매싱 방어를 위한 GCC 패치를 개발하였다. 이것은 스택 프레임에서 버퍼들을 로컬 포인터들과 함수 매개변수들의 뒤에 둠으로써 스택가드의 아이디어를 개선하였다. 이것은 임의적인 메모리 위치로 접근하는 것을 막음으로써 포인터의 오염을 피하게 도와주었다.
레드햇 엔지니어들은 ProPolice의 문제를 인식하고 2005년 GCC 4.1에 포함되는 스택 스매싱 보호를 재구현하였다. 이 작업은 codice_2 플래그를 도입하였는데 이것은 단지 몇몇 취약한 함수들을 보호하며, codice_3 플래그는 모든 함수들을 보호한다.
2012년, 구글 엔지니어들은보안과 성능의 밸런스를 맞추기 위해 codice_4 플래그를 구현하였다. 이 플래그는 codice_2보다 더 많은 종류의 취약한 함수들을 보호하지만 모든 함수는 아니며, codice_3보다 더 나은 성능을 제공한다. 이것은 GCC 4.9 버전부터 사용 가능하다.
StackGuard와 ProPolice는 (함수 포인터들을 오버플로우하는) 자동으로 할당된 구조체들에서 오버플로우를 방어하지 못한다. ProPolice는 함수 포인터들 전에 이러한 할당된 구조체들을 얻기 위해 적어도 할당 순서를 재배치할 것이다. 포인터 보호를 위한 독립된 메커니즘은 PointGuard에서 제안되었으며 마이크로소프트 윈도우에서 사용 가능하다.
마이크로소프트의 컴파일러 모음은 2003년부터 /GS 커맨드라인 스위치를 통해 버퍼 오버플로우 방어를 구현하였으며 이것은 버전 2005부터 디폴트로 활성화된다. /GS- 를 사용하면 방어를 비활성화할 수 있다.
Clang은 3가지의 버퍼 오버플로우 탐지자를 지원하는데, AddressSanitizer (-fsanitize=address),
-fsanitize=bounds, 그리고 SafeCode가 그것이다. 이러한 시스템들은 성능 패널티, 메모리 오버헤드 그리고 탐지된 버그들의 종류에 따라 다른 트레이드오프를 갖는다.