본문 바로가기
버퍼오버플로우

버퍼오버플로우

by paysmile 2016. 10. 11.

-버퍼 오버플로우란?

메모리를 다루는 데에 오류가 발생하여 잘못된 동작을 하는 프로그램 취약점이다.  컴퓨터 보안프로그래밍에서 이는 프로세스이터 버퍼저장할 때 프로그래머가 지정한 곳 바깥에 저장하는 것이다. 벗어난 데이터는 인접 메모리를 덮어 쓰게 되는데 다른 데이터가 포함되어 있을 수도 있는데, 손상을 받을 수 있는 데이터는 프로그램 변수와 프로그램 흐름 제어 데이터도 포함된다.

>시스템 해킹분야에서의 버퍼오버플로우는 메모리 오류를 이용해 타겟 프로그램의 실행흐름을 제어하고 최종적으로 공격자가 원하는 임의의 코드를 실행하는 것

* c언어에서는 변수에 값을 할당할 때 값의 크기가 변수의 메모리 경계를 벗어나지 않는지 검사하는 내부 과정이 없기 때문에 프로그래머의 실수나 에러로 충분히 취약점이 존재할 수 있게됨.

-동작 원리

 

스택 버퍼 오버플로우의 발생 원리

 

하나의 프로그램은 수많은 함수로 구성되어 있는데 이 함수가 호출될 때, 지역 변수와 복귀 주소(Return Address)가 스택(Stack)이라 하는 논리 데이터 구조에 저장됩니다.

복귀 주소가 저장되는 이유는 함수가 종료될 때 운영체제가 함수를 호출한 프로그램에 제어권을 넘겨야 하는데, 이 복귀주소가 없으면 함수가 종료된 후 어떤 명령어를 수행해야 할 지 모르기 때문입니다.

 

버퍼 오버플로우는 이 복귀 주소가 함수가 사용하는 지역 변수의 데이터에 의해 침범 당할 때 발생합니다. 프로그래머 실수로 지역 변수가 할당된 크기보다 더 많은 데이터를 입력하면(위 그림 b) 복귀 주소가 이 데이터에 의해서 다른 값으로 변경이 되기 때문에 함수가 종료될 때 엉뚱한 복귀 주소를 실행시키게 되어서 애플리케이션이 뜻하지 않게 종료됩니다.

더 문제는 악의적인 공격자가 이 버퍼 오버플로우 결함을 이용하는 경우입니다. 보통 버퍼 오버플로우 공격(Buffer Overflow Attack)이라고 하는데, 공격자가 이 취약점을 알고 데이터의 길이와 내용을 적절히 조정해서 버퍼 오버플로우를 일으켜서 특정 코드를 실행시키게 하는 해킹 기법에 사용됩니다.

 

 

 

 

 

-예제

⓵ 간단한 예제 코드

 

아래는 버퍼 오버플로우가 발생하는 C 코드입니다.

 

#include

int main(int argc, char* argv[])

{

char buffer[200];

strcpy(argv[0], buffer);

printf("Hello %s",buffer);

}

 

이 코드는 버퍼 오버플로우 취약성이 있는 코드입니다. 코드를 컴파일하고 실행시켰을 때 첫 번째 인자에 200자 이상의 문자를 입력하면 버퍼 오버플로우가 발생합니다. 이 경우 악의적인 공격자가 컴퓨터의 루트 권한을 탈취, 해당 컴퓨터를 자유자재로 다룰 수 있습니다.

(1)프로그램 실행시 메모리의 구조

<!--[if !supportEmptyParas]--> <!--[endif]-->

아래의 그림은 하나의 프로그램에 할당되는 메모리의 전체적인 모습입니다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

/--------------------/ 메모리의 높은 숫자의 주소

| |

| Stack |

| |

/------------------/

| |

| Heap |

| |

/--------------------/

| |

| Data |

| |

/--------------------/

| |

| Text |

| |

/--------------------/ 메모리의 낮은 숫자의 주소

<!--[if !supportEmptyParas]--> <!--[endif]-->

Heap영역이나 Data영역, Text영역에서 변수에 그 자리를 줄 때에는 "메모리의 낮은 주소"에서 부터 "메모리 높은 주소"로 순서대로 한다고 합니다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

스택이 거꾸로 자란다는 것은, 위에서 말한 다른 메모리의 영역과는 반대로 변수에 그 자리를 내어줄 때에 "메모리의 높은 주소"에서부터 "메모리 낮은 주소"로 한다는 것 입니다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

(2)예제(스택은 거꾸로 자란다)

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

int a;

int b;

int c; //각각 a, b, c라는 전역변수가 순서대로 선언되었습니다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

int main( void )

{

char buffer1[7];

chat buffer2[7]; //main함수 안에서 각각 buffer1, buffer2라는 배열이 선언되었습니다.

}

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

이러한 내용을 가진 프로그램이 수행될 때 전체메모리의 구조를 그림으로 보도록 하겠습니다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

/-------------------------/ 메모리의 높은 숫자의 주소

| ret |

| |

| sfp(4byte)|

| | Stack

| buffer1 |

| |

| buffer2 |

/-------------------------/

| | Heap

/-------------------------/

| c |

| b | Data

| a |

/-------------------------/

| | Text

/-------------------------/ 메모리의 낮은 숫자의 주소

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

스택영역과 데이타영역을 비교해보면 '스택은 거꾸로 자란다'는 말을 이해 할 수 있습니다. 먼저 선언된 순서에 따라 메모리에 자리를 잡는데 Data영역과 Stack영역이 반대로 진행되었습니다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

다시 한번 더 말하자면, 데이터영역은 변수가 선언된 순서대로 메모리의 낮은 주소에서 시작해서 할당이 되는데, 스택영역에서는 먼저 선언된 변수가 메모리 높은 주소에서 시작해서 할당 받는 것입니다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

3. 오버플로우

<!--[if !supportEmptyParas]--> <!--[endif]-->

void function( int a, int b, int c )

{

char buffer1[5];

chat buffer2[5];

}

<!--[if !supportEmptyParas]--> <!--[endif]-->

int main( void )

{

int super;

function( 1, 2, 3 );

}

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

이 소스를 보면 buffer1buffer2는 그 크기가 각각 5바이트로 정해놓았습니다. 그런데 5바이트를 넘는 것을 입력함으로써 버퍼오버플로우가 발생되게 하는 것입니다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

buffer25바이트가 넘는 문자열을 넣으면 스택은 다음과 같이 됩니다. buffer2"ABCDEFGHI"(9바이트)가 들어갔다고 가정한 결과입니다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportEmptyParas]--> <!--[endif]-->

메모리의 높은 숫자의 주소

/-------------------/ function함수 콜

| ret |

/-------------------/

| sfb(4byte) |

/-------------------/

| |

| I |

| H | buffer1[5]