본문 바로가기

CERT C/선언과 초기화

[CERT C/선언과 초기화] (3) 기억클래스 취약성

의미

기억클래스란 변수의 값이 어떤 종류의 메모리에 저장되는지를 지정하는 것을 의미한다. auto, register, static으로 구성되어있다.

 

   
auto 변수 함수, 블록 내부에서 사용되고 흔희 local 변수라고 불린다. 기억 클래스가 명시되지 않고 선언된 변수는 모두 자동 변수이다.

스택 공간을 사용하며, 함수나 블록에서 기억 영역이 확보되고, 벗어나면 소거됩니다.
** return 문에서 return 되는 값은 외부에서 기억영역을 호가보하게된다.
register 변수 CPU가 연산 시 데이터를 임시로 저장하는데 사용하는 작고 빠른 기억장소이다.

2개까지만 선언 가능하고, 초과된 변수는 AUTO 변수로 지정된다. 레지스터에는 주소가 없으므로 참조가 불가능하다.
static 변수 프로그램이 종료될 때까지 값을 유지하며 처음 실행 시 가장 먼저 컴파일되고 초기화가 없으면 0으로 초기화 된다. 

스택이 아닌 정적 데이터 영역을 사용한다.
global 변수 함수 외부에서 선언되고 프로그램 전체의 영역에서 유효하다.

스택이 아닌 정적 데이터 영역을 사용한다.

동작 방식

어떤 기억 클래스를 사용했을 때, 프로그램 내부 변수들이 데이터 영역, 스택 영역, 힙 영역, 텍스트 영역 (Read only 영역) 등 어디에 할당되는지 그 동작 방식을 이해하면서 프로그램을 작성해야 원하지 않는 행동들이 유발되는 상황을 줄일 수 있다.

문제 코드 1.  적절한 기억클래스를 사용하라

출력을 예상해보기

#include <stdio.h>
#include <string.h>

void TestFunction();
void innocuous();

const char *p;

int main()
{
    TestFunction();
    innocuous();
    printf("p=%s\n", p);
    return 0;
}

void TestFunction()
{
    const char str[] = "This will change";
    p = str;
    printf("p=%s : str=%s\n", p, str);
}

void innocuous()
{
    const char str[] = "Surprise, surprise";
    printf("p=%s : str=%s\n", p, str);
}

 

출력 :

p=This will change : str=This will change

p=Surprise, surprise : str=Surprise, surprise

p=Surprise, surpri�#

 

위 코드를 보면 3번째에 원하지 않은 출력 (동작)을 하는 것을 볼 수 있다.

취약점 분석 및 해결 코드 1

분석 : const char를 가리키는 p 포인터가 TestFunction 내부 스택 영역에서 생성되는 str 문자열은 데이터 영역에 값이 써지고 str 이라는 변수는 TestFunction이 끝날 때 수명을 다한다. 하지만 p는 수명이 다한 str이 써놓은 (This will change 의 가장 첫번째 주소 ) 데이터 영역을 그대로 가리키고 있어서 정의되지 않은 행동을 유발하게 되었다.

 

-> 또한 수명을 다한 데이터를 가리키는 포인터로 데이터에 접근하는 일은 보안 상에서도 악용될 수 있는 큰 취약점이므로 반드시 해결을 해야한다.

 

*** 블록 밖에서 선언된 const 키워드 상수는 data 영역에 저장되고

*** 블록 안에서 선언된 const 키워드 상수는 문자열의 경우 data 영역, ( int, double, float ) 형 등의 경우 stack 영역에 저장된다.

 

#include <stdio.h>
#include <string.h>

void TestFunction();
void innocuous();

const char *p;

int main()
{
    TestFunction();
    innocuous();
    printf("p=%s\n", p);
    return 0;
}

void TestFunction()
{
    const char str[] = "This will change";
    p = str;
    printf("p=%s : str=%s\n", p, str);
    p = NULL;
}

void innocuous()
{
    const char str[] = "Surprise, surprise";
    printf("p=%s : str=%s\n", p, str);
}

str의 수명이 끝나는 지점에서 그 str 변수를 가리키던 전역 변수 포인터 p가 NULL을 가리키도록 바꾸고 함수를 마무리함으로 해결한다.

 

혹은, p를 사용하는 위치에서만 auto 변수 즉, local 변수로 선언하여서 해결할 수도 있다.

 

POINT

  • 프로그램 내부 변수들이 데이터 영역, 스택 영역, 힙 영역, 텍스트 영역 (Read only 영역) 등 어디에 할당되는지 확인하자. 헷갈린다면 바로 구글에서 찾아보자.
  • 수명을 다한 데이터를 가리키는 포인터는 정상적이지 않은 동작을 한다는 것을 명심하자.

참조

 


Rec. 02. Declarations and Initialization (DCL)

wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87151966