C가 버퍼 오버플로 경향이 적은 이유는 무엇입니까?


23

저는 실험실에서 실험실에서 우리에게 제공하는 코드에 버퍼 오버플로 악용을 수행하는 코스를 진행하고 있습니다. 이것은 스택의 함수에 대한 리턴 주소를 변경하여 다른 함수로 리턴하는 것과 같은 간단한 악용에서부터 프로그램 레지스터 / 메모리 상태를 변경하지만 호출 한 함수로 돌아가는 코드까지 다양합니다. 당신이 호출 한 함수는 익스플로잇에 대해 완전히 알지 못합니다.

나는 이것에 대해 약간의 연구를 했으며, Wii에서 homebrew를 실행 하고 iOS 4.3.1을위한 무제한 탈옥 과 같은 것들에서 이러한 종류의 공격은 지금까지 거의 모든 곳에서 사용됩니다.

내 질문은 왜이 문제를 해결하기가 어렵습니까? 이것은 수백 가지를 해킹하는 데 사용되는 주요 익스플로잇입니다. 그러나 허용 된 길이를 초과하는 입력을 잘라 내고 입력하는 모든 입력을 삭제하는 것만으로 쉽게 고칠 수있을 것 같습니다.

편집 : 또 다른 대답을 고려해야합니다. C 작성자가 라이브러리를 다시 구현하여 이러한 문제를 해결하지 않는 이유는 무엇입니까?

답변:


35

그들은 도서관을 고쳤습니다.

최신 C 표준 라이브러리에는보다 안전한 strcpy,, strcat등의 변형이 포함되어 있습니다 sprintf.

대부분의 유닉스 인 C99 시스템에서는 이름이 strncatand와 같은 snprintf"n" 을 발견 할 수 있습니다 . "n"은 버퍼 크기 또는 복사 할 최대 요소 수인 인수를 취함을 나타냅니다.

이러한 기능을 사용하면 많은 작업을보다 안전하게 처리 할 수 ​​있지만 사용 편의성은 그리 좋지 않습니다. 예를 들어 일부 snprintf구현에서는 버퍼가 null로 종료되는 것을 보장하지 않습니다. strncat복사하는 데 많은 요소가 필요하지만 많은 사람들이 실수로 dest 버퍼의 크기를 전달합니다.

Windows에서, 하나는 종종 발견 strcat_s, sprintf_s은 "_s"접미사 표시 "안전". 이것들도 C11의 C 표준 라이브러리에 들어 갔으며 오버플로가 발생했을 때 발생하는 상황을보다 잘 제어 할 수 있습니다 (예 : 잘림 vs. assert).

많은 벤더들이 asprintfGNU libc에서 와 같이 훨씬 더 비표준적인 대안을 제공 하는데, 이는 적절한 크기의 버퍼를 자동으로 할당합니다.

"C를 바로 고칠 수있다"는 생각은 오해입니다. C 수정은 문제가 아니며 이미 완료되었습니다. 문제는 무지하거나 피곤하거나 급한 프로그래머가 작성한 수십 년의 C 코드 또는 보안이 중요하지 않은 컨텍스트에서 보안이 수행되는 컨텍스트로 포팅 된 코드를 수정하는 것입니다. 최신 컴파일러 및 표준 라이브러리로 마이그레이션하면 문제를 자동으로 식별하는 데 도움이되지만 표준 라이브러리를 변경해도이 코드를 수정할 수는 없습니다.


11
언어가 아닌 프로그래머에게 문제를 겨냥한 +1
Nicol Bolas 2012

8
@Nicol : "문제는 프로그래머들"이라고 말하는 것은 부당하게 환원 주의자입니다. 문제는 수년간 (수십 년 동안) C가 안전 코드보다 안전하지 않은 코드를 작성하는 것이 더 쉬워졌으며, 특히 "안전"에 대한 정의가 다른 언어 표준보다 빠르게 발전했으며 해당 코드가 여전히 존재한다는 것입니다. 이를 단일 명사로 줄이려는 경우 문제는 "프로그래머가 아니라"1970-1999 libc "입니다.

1
이러한 문제 를 해결 하기 위해 지금 가지고있는 도구를 사용하는 것은 여전히 ​​프로그래머의 책임입니다 . 반나절 정도 걸리고 이러한 것들에 대한 소스 코드를 통해 약간의 grepping을 수행하십시오.
Nicol Bolas

1
@Nicol : 잠재적 인 버퍼 오버 플로우를 감지하는 것은 사소한 일이지만, 실제 위협인지 확신하는 것은 사소한 일이 아니며, 버퍼가 오버플로 된 경우 어떻게해야 하는지를 해결하는 것은 사소한 일이 아닙니다. 오류 처리는 종종 고려되지 않았거나 예상되지 않았지만 예기치 않은 방식으로 모듈의 동작을 변경할 수 있으므로 개선을 "빠른"구현할 수 없습니다. 우리는 방금 수백만 줄의 레거시 코드베이스 에서이 작업을 수행했으며, 가치있는 운동이지만 많은 시간과 돈이 들었습니다.
mattnz February

4
@NicolBolas : 확실하지 어떤 종류의 상점 작동하지만 생산 사용을 위해 C를 쓴 마지막 장소가 완벽한을 수행하는, 그것을 검토, 상세 설계 문서를 개정 코드를 변경, 테스트 계획을 개정, 테스트 계획을 검토 필요 시스템 테스트, 테스트 결과 검토 및 고객 사이트에서 시스템 재 인증 더 이상 존재하지 않는 회사를 위해 작성된 다른 대륙의 통신 시스템을위한 것입니다. 마지막으로 나는 소스가 RCS는 QIC 테이프에 보관 있었다, 알고 해야 당신이 적절한 테이프 드라이브를 찾을 수 있다면 여전히 읽을 수.
TMN

19

C가 실제로 설계 상 "오류가 발생하기 쉽다"고 말하는 것은 실제로 부정확하지 않습니다 . getsC 와 같은 약간의 실수 외에도 C 언어는 사람들을 처음으로 C로 이끌어주는 주요 기능을 잃지 않으면 다른 방법이 될 수 없습니다.

C는 일종의 "휴대용 어셈블리"역할을하는 시스템 언어로 설계되었습니다. C 언어의 주요 기능은 고급 언어와 달리 C 코드가 실제 기계 코드와 매우 밀접하게 매핑된다는 것입니다. 다시 말해, ++i일반적으로 inc명령 일 뿐이며 C 코드를 보면 프로세서가 런타임시 수행 할 작업에 대한 일반적인 아이디어를 얻을 수 있습니다.

그러나 암시 적 범위 검사를 추가하면 프로그래머가 요청하지 않았거나 원하지 않을 수있는 오버 헤드가 많이 발생합니다. 이 오버 헤드는 각 어레이의 길이를 저장하는 데 필요한 추가 스토리지 또는 모든 어레이 액세스에서 어레이 경계를 확인하기위한 추가 명령어를 뛰어 넘습니다. 포인터 산술은 어떻습니까? 아니면 포인터를받는 함수가 있다면 어떨까요? 런타임 환경은 해당 포인터가 합법적으로 할당 된 메모리 블록의 범위 내에 있는지 알 수있는 방법이 없습니다. 이를 추적하려면 현재 할당 된 메모리 블록 테이블에 대해 각 포인터를 검사 할 수있는 심각한 런타임 아키텍처가 필요합니다.이 시점에서 우리는 이미 Java / C # 스타일 관리 런타임 영역에 들어가고 있습니다.


12
솔직히 사람들이 왜 C가 "안전하지 않은지"물어 보면 어셈블리가 "안전하지 않다"고 불평하는지 궁금해집니다.
벤 Brocka

5
C 언어는 Digital Equipment Corporation PDP-11 시스템의 휴대용 어셈블리와 매우 유사합니다. 동시에 Burroughs 시스템은 CPU에서 배열 경계 검사를 수행하여 프로그램을 쉽게 얻을 수있었습니다. 하드웨어에서 배열 검사는 Rockwell Collins 하드웨어에서 주로 사용됩니다 (주로 항공에서 사용)
Tim Williscroft

15

실제 문제는 이러한 종류의 버그를 수정하기가 쉽지 않다는 것이 아니라 너무 쉽게 만들 수 있다는 것입니다. strcpy, 를 사용할 sprintf수있는 가장 단순한 방법으로 친구와 친구 를 사용하는 경우 아마도 버퍼 오버플로에 대한 문을 열었습니다. 그리고 코드를 잘 검토하지 않는 한 누군가가 악용하기 전까지는 아무도 알지 못합니다. 이제 평범한 프로그래머가 많고 대부분 시간이 지남에 따라 버퍼 오버플 로로 가득 찬 코드에 대한 레시피가 있으므로 간단하게 수정하기가 어렵습니다. 그들 중 많은 사람들이 숨어 있습니다.


3
"아주 좋은 코드 리뷰"가 필요하지 않습니다. sprintf를 금지하거나 sprintf를 sizeof () 및 포인터 크기 등의 오류를 사용하는 것으로 재정의해야합니다. 코드 검토가 필요하지 않으며 SCM 커밋으로 이러한 종류의 작업을 수행 할 수 있습니다 갈고리와 grep.

1
@JoeWreschnig : sizeof(ptr)는 일반적으로 4 또는 8입니다. 그것은 또 다른 C 제한 사항입니다. 포인터가 주어지면 배열의 길이를 결정할 방법이 없습니다.
MSalters

@MSalters : 예, int [1] 또는 char [4]의 배열 또는 오 탐지 일 수도 있지만 실제로는 해당 함수로 해당 크기의 버퍼를 처리하지 않습니다. (이론적으로 이론적으로 말하고있는 것은 아닙니다. 저는이 접근법을 사용한 4 년 동안 큰 C 코드 기반에서 작업했습니다. 나는 문자로 스프린트하는 한계에 결코 부딪치지 않았습니다 [4].)

5
@BlackJack : 대부분의 프로그래머는 어리석지 않습니다-만약 당신이 그들을 강제로 크기를 넘기면, 그들은 올바른 것을 통과 할 것입니다. 강제하지 않는 한 크기도 전달하지 않습니다. 정적 또는 자동 크기 인 경우 배열의 길이를 반환하지만 포인터가 제공되면 오류를 반환하는 매크로를 작성할 수 있습니다. 그런 다음 sprintf를 재정 의하여 크기를 제공하는 해당 매크로로 snprintf를 호출하십시오. 이제 알려진 크기의 어레이에서만 작동하는 sprintf 버전이 있으며 프로그래머는 수동으로 지정된 크기의 snprintf를 강제로 호출합니다.

1
이러한 매크로의 간단한 예 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / (sizeof(a) != sizeof(void *))는 컴파일 타임을 0으로 나누는 것입니다. 내가 Chromium에서 처음 본 또 다른 영리한 것 중 하나는 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / !(sizeof(a) % sizeof((a)[0]))일부 오탐 (false negative)에 대해 오탐 (false positive ) 을 교환하는 것입니다. 불행히도 char []에는 쓸모가 없습니다. blogs.msdn.com/b/ce_base/archive/2007/05/08/…와 같이 다양한 컴파일러 확장을 사용하여 더욱 안정적으로 만들 수 있습니다 .

7

C는 문제를 해결하는 데 유용한 도구를 거의 제공하지 않기 때문에 버퍼 오버플로를 수정하기가 어렵습니다. 그것은 기본 버퍼가 더 보호를 제공 없다는 기본적인 언어 결함의 C ++와 함께했던 것처럼 그것은 우수한 제품으로 교체하는 거의하지 않을 경우 완전히 불가능 std::vector하고 std::array, 그리고 는 버퍼 오버 플로우 찾아도 디버그 모드에서 어렵다.


13
"언어 결함"은 끔찍한 편견입니다. 라이브러리가 경계 검사를 제공하지 않았다는 것은 결함이었습니다. 언어가 오버 헤드를 피하기위한 의식적인 선택이 아니라는 것. 이러한 선택은보다 높은 수준의 구성 std::vector을 효율적으로 구현할 수 있도록하는 것의 일부입니다 . 그리고 vector::operator[]안전성에 속도 같은 선택을합니다. 안전성은 vector현대 C 라이브러리가 사용하는 것과 동일한 접근 방식 인 크기로 쉽게 카트를 옮길 수 있다는 점 에서 나옵니다.

1
@Charles : "C는 표준 라이브러리의 일부로 동적 확장 버퍼를 제공하지 않습니다." 아니, 이건 아무 상관 없어 첫째, C는이를 통해이를 제공합니다 realloc(C99는 거의 항상 선호하는 자동 변수를 통해 런타임으로 결정되지만 일정한 크기를 사용하여 스택 배열의 ​​크기를 조정할 수 있습니다 char buf[1024]). 둘째, 문제는 확장 버퍼와는 아무런 관련이 없으며 버퍼가 크기를 가지고 있는지 여부와 관련이 있으며 액세스 할 때 해당 크기를 확인하십시오.

5
@ 조 : 문제는 그리 많지 않아 네이티브 배열이 깨졌습니다. 교체가 불가능합니다. 시작을 위해 vector::operator[]디버그 모드에서 경계 검사를 수행합니다. 기본 배열은 할 수없는 일이며 두 번째로 C에서는 기본 배열 유형을 경계 검사를 수행 할 수 있는 유형으로 교체 할 수 있는 방법이 없습니다. 템플릿이없고 연산자가 없기 때문입니다. 과부하. C ++에서에서 T[]로 이동 std::array하려면 실제로 typedef를 바꿀 수 있습니다. C에서는 그것을 달성 할 수있는 방법이 없으며 인터페이스는 물론 동등한 기능을 가진 클래스를 작성할 수 없습니다.
DeadMG

3
@Joe : 정적으로 크기를 조정할 수 없다는 점을 제외하고는 일반적인 것으로 만들 수 없습니다. C ++에서 std::vector<T>와 같은 역할을 수행하는 라이브러리를 C로 작성할 수 없습니다 std::array<T, N>. 표준 라이브러리가 아닌 라이브러리를 설계하고 지정할 수있는 방법은 없습니다.
DeadMG

1
"정적으로 크기를 조정할 수 없습니다."라는 말의 의미를 잘 모르겠습니다. 그 용어를 std::vector사용할 때 정적으로 크기를 조정할 수도 없습니다. 일반에 관해서는 void C (추가, 제거, 크기 조정) 및 기타 구체적으로 작성된 모든 것의 소수의 기본 작업 인 C가 필요로하는 것처럼 일반으로 만들 수 있습니다. C에 C ++ 스타일 제네릭이 없다고 불평하려는 경우 안전한 버퍼 처리 범위를 벗어납니다.

7

문제는 C 언어 가 아닙니다 .

극복해야 할 유일한 주요 장애물 인 IMO는 C를 잘못 가르치는 것 입니다. 수십 년간의 나쁜 연습과 잘못된 정보가 참조 매뉴얼과 강의 노트에 체계화되어 처음부터 새로운 세대의 프로그래머의 마음을 독살했습니다. 학생들은 같은 "쉽게"I / O 기능에 대한 간략한 설명을 제공하는 gets1 또는 scanf다음 자신의 장치를 떠났다. 해당 도구가 실패 할 수있는 위치 또는 방법 또는 이러한 실패를 방지하는 방법에 대해서는 설명하지 않습니다. 그들은 사용 fgetsstrtol/strtod"고급"도구로 간주되기 때문입니다. 그런 다음 그들은 전문 세계에 파급되어 혼란을 겪습니다. 숙련 된 많은 프로그래머들이 뇌 손상 교육을 받았기 때문에 더 잘 알지 못합니다. 미쳤다. 여기와 스택 오버플로 및 다른 사이트에서 질문을하는 사람이 단순히 자신이 무엇을 말하고 있는지 모르는 사람이 가르치고 있음을 분명히 알 수 있으며 물론 말할 수는 없습니다. "교수는 틀렸어."그는 교수이고 당신은 인터넷상의 사람 일뿐 입니다.

그리고 당신은 "음, 언어 표준에 따라 ..."로 시작하는 모든 대답을 무시하는 군중을 가지고 있습니다. 왜냐하면 그들은 실제 세계 에서 일하고 있고 그들에 따르면 표준은 실제 세계에 적용되지 않기 때문 입니다 . 나는 단지 나쁜 교육을받은 사람을 다룰 수 있지만 무지한 사람 이라고 주장 하는 사람은 그 산업계의 역경 일뿐입니다.

보안 코드 작성에 중점을 두어 언어를 올바르게 학습하면 버퍼 오버 플로우 문제가 발생하지 않습니다 . "단단하지"않고 "고급"도 아니고 그냥 조심하고 있습니다.

그렇습니다.


1 영원히 레거시 코드 40 년 '의 가치에 숨어 하겠지만 어떤은, 다행히도 마지막으로, 언어 사양에서 빠지게되었습니다.


1
나는 대부분 당신에게 동의하지만, 당신은 여전히 ​​약간 불공평하다고 생각합니다. 우리가 "안전한"것으로 생각하는 것은 시간의 함수이기도합니다. (저는 당신이 저보다 훨씬 더 오랜 시간 동안 전문 소프트웨어 개발자 였음을 알고 있습니다. 10 년 후 누군가 2012 년에 왜 모든 사람이 DoS 가능 해시 테이블 구현을 사용했는지에 대해 같은 대화를 나눌 것입니다. 보안에 대해 아는 것이 없습니까? 가르치는 데 문제가 있다면, 우리는 "우수한"연습을 가르치는 데 너무 많은 초점을 맞추는 것이지, 그 최상의 방법 자체가 진화하는 것은 아닙니다.

1
솔직 해지자. 으로 안전한 코드를 작성할 sprintf 있지만 언어에 결함이 없음을 의미하지는 않습니다. C는 결함 및 되는 결함 - 모든 언어처럼 - 그리고 우리가 그들을 해결하기 위해 계속할 수 있도록 우리가 그 결함을 인정하는 것이 중요합니다.

@JoeWreschnig-더 큰 요점에 동의하지만 DoS 가능 해시 테이블 구현과 버퍼 오버런 간에는 질적 인 차이가 있다고 생각합니다. 전자는 주변 환경의 변화에 ​​기인 할 수 있지만, 두 번째는 변명의 여지가 없습니다. 버퍼 오버런은 코딩 오류, 기간입니다. 그렇습니다. C에는 블레이드 가드가 없으며 부주의 한 경우 당신을 잘라 줄 것입니다. 우리는 그것이 언어의 결함인지 아닌지 논쟁 할 수 있습니다. 그의는 거의 학생들이 주어진다는 사실에 직교 어떤 그들이 언어를 학습 할 때 안전 지침을.
John Bode

5

문제는 프로그래머의 무능함보다는 관리적 근시안입니다. 90,000 줄 응용 프로그램은 필요 기억 것으로 안전하지 않은 작업을 완전히 불안정. 기본적으로 안전하지 않은 문자열 처리로 작성된 응용 프로그램이 100 % 완벽 할 가능성은 거의 없습니다. 이는 안전하지 않을 수 있음을 의미합니다.

문제는 안전하지 않은 비용이 올바른 수취인에게 청구되지 않거나 (앱을 판매하는 회사는 거의 구매 가격을 환불하지 않아도 됨) 결정이 내려 질 때 명확하게 보이지 않는다는 것입니다 ( "우리는 배송해야합니다" 3 월에 무슨 일이 있어도! "). 회사 이익이 아닌 사용자에게 장기 비용과 비용을 고려하면 C 또는 관련 언어로 작성하는 것이 훨씬 비싸고 아마도 너무 비싸서 많은 사람들에게 분명히 잘못된 선택이 될 것입니다. 오늘날의 전통적인 지혜는 그것이 필수라고 말합니다. 그러나 업계에서 아무도 원하지 않는 훨씬 더 엄격한 소프트웨어 책임이 도입되지 않으면 변경되지 않습니다.


-1 : 모든 악의 근원 인 비난 관리는 특히 건설적이지 않습니다. 역사를 조금 덜 무시합니다. 대답은 마지막 문장에서 거의 구속됩니다.
mattnz

보안에 관심이 있고 지불하고자하는 사용자는보다 엄격한 소프트웨어 책임을 도입 할 수 있습니다. 보안 침해에 대해 심각한 처벌을가함으로써 도입 될 수 있습니다. 사용자가 보안 비용을 기꺼이 지불하고 싶지만 시장 기반 솔루션은 효과가 있습니다.
David Thornley

4

C를 사용하는 가장 큰 장점 중 하나는 원하는 방식으로 메모리를 조작 할 수 있다는 것입니다.

C를 사용하는 것의 가장 큰 약점 중 하나는 메모리를 원하는대로 조작 할 수 있다는 것입니다.

안전하지 않은 기능에는 안전한 버전이 있습니다. 그러나 프로그래머와 컴파일러는 엄격하게 사용하지 않습니다.


2

C 제작자가 라이브러리를 다시 구현하여 이러한 문제를 해결하지 않는 이유는 무엇입니까?

아마도 C ++은 이미이 작업을 수행했으며 C 코드와 호환됩니다. 따라서 C 코드에서 안전한 문자열 유형을 원하면 std :: string을 사용하고 C ++ 컴파일러를 사용하여 C 코드를 작성하십시오.

기본 메모리 하위 시스템은 가드 블록과 유효성 검사를 도입하여 버퍼 오버플로를 방지하는 데 도움이됩니다. 따라서 모든 할당에는 4 바이트의 'fefefefe'가 추가되며 이러한 블록을 쓸 때 시스템은 워 블러를 던질 수 있습니다. 메모리 쓰기를 막을 수는 없지만 문제가 발생하여 수정해야 함을 보여줍니다.

문제는 오래된 strcpy 등 루틴이 여전히 존재한다는 것입니다. strncpy 등을 위해 제거 된 경우 도움이 될 것입니다.


1
strcpy 등을 완전히 제거하면 증분 업그레이드 경로가 더욱 어려워 져 사람들이 전혀 업그레이드하지 못하게됩니다. 이제는 C11 컴파일러로 전환 한 다음 _s 변형 사용을 시작한 다음 비 _s 변형을 금지 한 다음 기존 사용을 수정하여 실제 실행 가능한 기간에 관계없이 수행 할 수 있습니다.

-2

오버플로 문제가 해결되지 않은 이유를 이해하는 것은 간단합니다. C는 몇 가지 영역에서 결함이있었습니다. 그 당시 그 결함은 견딜 수 있거나 특징으로 보였습니다. 이제 수십 년 후 이러한 결함은 고칠 수 없습니다.

프로그래밍 커뮤니티의 일부는 이러한 구멍을 막고 싶지 않습니다. 문자열, 배열, 포인터, 가비지 수집에서 시작되는 모든 불꽃 전쟁을 살펴보십시오 ...


5
LOL, 끔찍하고 잘못된 대답.
Heath Hunnicutt

1
이것이 왜 나쁜 대답인지 설명하기 위해 : C에는 실제로 많은 결함이 있지만 버퍼 오버플로 등을 허용하는 것은 기본 언어 요구 사항과 거의 관련이 없습니다. C 작업을 수행하고 버퍼 오버플로를 허용하지 않는 언어를 디자인하는 것은 불가능합니다. 커뮤니티의 일부는 C가 허용하는 기능을 포기하기를 원하지 않습니다. 또한 이러한 문제 중 일부를 피하는 방법에 대해서는 의견이 분분하여 프로그래밍 언어 설계에 대한 완전한 이해가 없다는 것을 보여줍니다.
David Thornley

1
@DavidThornley이 : 하나는 C의 일을하지만, 그렇게 일을하는 일반적인 관용적 방법이 적어도 것을 만들 수있는 언어를 설계 할 수 있도록 컴파일러가 합리적으로 효율적으로 버퍼 오버 플로우 확인을, 컴파일러는 그렇게하도록 선택해야합니다. memcpy()사용 가능하고 배열 세그먼트를 효율적으로 복사하는 표준 수단이 되는 것에는 큰 차이가 있습니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.