보안/시스템 해킹

[ 시스템 해킹 ] Computer Science: 리눅스 메모리 구조

haena02 2022. 7. 14. 04:17
반응형

CPU는 실행할 명령어와 명령어 처리에 필요한 데이터를 메모리에서 읽고, 명령어 집합 구조(ISA)에 따라 이를 처리합니다. 그리고 연산의 결과를 다시 메모리에 적재한다.

만약 공격자가 메모리를 악의적으로 조작할 수 있다면 이에 의해 CPU도 잘못된 동작을 할 수 있다.

이를 메모리가 오염됐다고 표현하며, 이를 유발하는 취약점을 메모리 오염 취약점이라고 부른다. 

이를 이해하기 위한 배경 지식으로 리눅스 메모리 구조(Memory Layout)에 대해 알아보고 프로세스 가상메모리의 각 구역이 어떤 정보를 담고 있는지 이해할 수 있다.

 

1. 세그먼트(Segment)

 

리눅스에서는 프로세스의 메모리를 크게 5가지의 세그먼트로 구분한다.

세그먼트란 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것인데, 크게 코드 세그먼트, 데이터 세그먼트, BSS 세그먼트, 힙 세그먼트, 그리고 스택 세그먼트로 구분한다.

운영체제가 메모리를 용도별로 나누면, 각 용도에 맞게 적절한 권한을 부여할 수 있고, CPU는 메모리에 대해 권한이 부여된 행위만 할 수 있습니다.

 

 

 

1.1 코드 세그먼트 (Code Segment)

 

코드 세그먼트 실행 가능한 기계 코드가 위치하는 영역으로 텍스트 세그먼트라고도 불린다.

프로그램이 동작하려면 코드를 실행할 수 있어야 하므로 이 세그먼트에는 읽기 권한과 실행 권한이 부여된다.

쓰기 권한이 있으면 공격자가 악의적인 코드를 삽입하기가 쉬워지므로, 이 세그먼트에 쓰기 권한을 제거합니다.

 

int main() { 

	return 31337; 
    
}

 

정수 31337을 반환하는 main함수가 컴파일 되면 기계 코드로 변환되고 이 기계 코드가 코드 세그먼트에 위치하게 된다.

 

1.2 데이터 세그먼트 (Data Segment)

 

데이터 세그먼트에는 컴파일 시점에 값이 정해진 전역 변수 및 전역 상수들이 위치한다.

CPU가 이 세그먼트의 데이터를 읽을 수 있어야 하므로, 읽기 권한이 부여된다.

 

전역 변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터들이 위치하는 쓰기가 가능한  세그먼트와

실행되면서 값이 변하면 안되는 데이터들이 위치하는 rodata(read-only data) 세그먼트가 존재한다.

rodata에는 전역 상수와 같은 것이 들어간다.

 

아래는 데이터 세그먼트에 포함되는 여러 데이터의 유형이다.

 str_ptr은 “readonly”라는 문자열을 가리키고 있는데, 이 문자열은 상수 문자열로 취급되어 rodata에 위치하며, 이를 가리키는 str_ptr은 전역 변수로서 data에 위치한다.

int data_num = 31337;                       // data
char data_rwstr[] = "writable_data";        // data
const char data_rostr[] = "readonly_data";  // rodata
char *str_ptr = "readonly";  // str_ptr은 data, 문자열은 rodata

int main() { ... }

 

 

1.3 BSS 세그먼트 (BSS Segment, Block Started By Symbol Segment)

 

BSS 세그먼트 컴파일 시점에 값이 정해지지 않은 전역 변수가 위치하는 메모리 영역이다.

여기에는 전역변수 등이 포함되며 프로그램이 시작될 때, 모두 0으로 값이 초기화된다. 

이 세그먼트에는 읽기 권한 및 쓰기 권한이 부여된다.

 

아래 코드에서 초기화되지 않은 전역 변수인 bss_data가 BSS 세그먼트에 위치하게 됩니다.

int bss_data;
int main() {
  printf("%d\n", bss_data);  // 0
  return 0;
}

 

 

1.4  스택 세그먼트 (Stack Segment)

 

스택 세그먼트는 프로세스의 스택이 위치하는 영역이다.

함수의 인자나 지역 변수와 같은 임시 변수들이 실행중에 여기에 저장된다.

 

스택 세그먼트는 스택 프레임이라는 단위로 사용된다.

스택 프레임은 함수가 호출될 때 생성되고, 반환될 때 해제된다. 

 

아래의 코드에서 유저가 입력한 choice에 따라 call_true()가 호출될 수도, call_false()가 호출될 수도 있다.

 

스택 세그먼트는 스택 프레임 단위로 사용되고 확장될 때 기존 주소보다 낮은 주소로 확장된다.

이 영역에는 CPU가 자유롭게 값을 읽고 쓸 수 있어야 하므로, 읽기와 쓰기 권한이 부여된다.

 

아래의 코드에서는 지역변수 choice가 스택에 저장되게 된다.

void func() {
  int choice = 0;
  scanf("%d", &choice);
  
  if (choice)
    call_true();
  else
    call_false();
  return 0;
}

 

 

1.5 힙 세그먼트 (Heap Segment)

 

힙 세그먼트는 힙 데이터가 위치하는 세그먼트이다.

스택과 마찬가지로 실행중에 동적으로 할당될 수 있으며, 일반적으로 읽기와 쓰기권한이 부여된다.

 

아래 예제 코드는 heap_data_ptr에 malloc()으로 동적 할당한 영역의 주소를 대입하고, 이 영역에 값을 쓴다.

heap_data_ptr은 지역변수이므로 스택에 위치하며, malloc으로 할당받은 힙 세그먼트의 주소를 가리킨다.

int main() {
  int *heap_data_ptr =
      malloc(sizeof(*heap_data_ptr));  // 동적 할당한 힙 영역의 주소를 가리킴
  *heap_data_ptr = 31337;              // 힙 영역에 값을 씀
  printf("%d\n", *heap_data_ptr);  // 힙 영역의 값을 사용함
  return 0;
}
반응형