본문 바로가기
문제 풀이/[DreamHack]

[DreamHack] System Hacking Stage2 - Background: Linux Memory Layout

by 조랩 2022. 12. 19.

Lecture

 

세그먼트(Segment)

- 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것 이다.

- 리눅스에서는 프로세스의 메모리를 크게 5가지의 세그먼트(코드 세그먼트, 데이터 세그먼트, BSS 세그먼트, 힙 세그먼트, 스택 세그먼트)로 나뉜다.

 

이렇게 나누는 이유?

각 용도에 맞게 적절한 권한을 부여할 수 있다는 장점이 있기 때문!

 

권한의 종류?

읽기, 쓰기, 실행 세 가지가 존재한다.

CPU는 메모리에 대해 권한이 부여된 행위만 할 수 있다.

 

예를 들어....

데이터 적재가 되는 곳에는 읽기 권한이 부여되고, 실행 대상이 아니기 때문에 실행 권한은 부여되지 않는다!

 

코드 세그먼트(Code Segment)

- 실행 가능한 기계 코드가 위치하는 영역이다. 

- 다른 말로 텍스트 세그먼트(Text Segment)라고도 불린다.

- 프로그램이 동작하기 위해서는 코드를 실행해야 하므로 읽기 권한과 실행 권한이 부여된다.

- 악의적 코드 삽입 방지 등 공격자의 접근을 어렵게 하기 위해서 쓰기 권한은 제거된다.

 

def func(): return 31337

 

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

 

데이터 세그먼트(Data Segment)

컴파일 시점에 값이 정해진 전역변수 및 전역 상수들이 위치한다.

- 이 세그먼트의 데이터를 읽어야 하므로 읽기 권한이 부여된다.

- 데이터 세그먼트는 쓰기가 가능한 세그먼트와 쓰기가 불가능한 세그먼트로 분류된다.

 

쓰기가 가능한 데이터 세그먼트

- 전역 변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터들이 위치한다.

- data 세그먼트라 부른다

 

쓰기가 불가능한 데이터 세그먼트

- 프로그램이 실행되면서 값이 변하면 안되는 데이터들이 위치한다.

- rodata(read-only-data) 세그먼트라 부른다.

 

예시)

int data_num = 31337;                            // data
char data_rwstr[] = "writeable_data";            // data
const char data_rostr[] = "readonly_data";       // rodata
char *str_ptr = "readonly";                      // str_ptr: data, "readonly": rodata

int main() { . . . }

 

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

- 컴파일 시점에 값이 정해지지 않은 전역 변수가 위치한다.

- 프로그램이 시작될 때, 모두 0으로 값이 초기화된다.

- 읽기 권한 및 쓰기 권한이 부여된다.

int bss_data;

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

 

스택 세그먼트(Stack Segment)

- 프로세스의 스택이 위치한다.

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

- 읽기 권한과 쓰기 권한이 부여된다.

 

스택 프레임(Stack Frame)?

- 함수가 호출될 때 생성되고, 함수가 반환될 때 해제된다.

 

void func(){

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

위의 코드를 보면...

- 사용자의 입력에 따라 call_true(), call_false()가 호출된다.

- 따라서, 일반적으로 실행한 프로세스가 얼마 만큼의 스택 프레임을 사용할지 계산하는것은 불가능하다.

- 그래서 운영체제는 프로세스를 시작할 때 작은 크기의 스택 세그먼트를 먼저 할당한 후, 부족할 경우 이를 확장한다.

- 종종, '아래로 자란다'라는 말을 사용하기도 하는데, 이는 스택이 확장될 때 마다 기존 주소보다 '낮은 주소'로 확장되기 때문이다.

 

위의 코드에서는 choice가 스택에 저장된다.

 

힙 세그먼트(Heap Segment)

힙 데이터가 위치한다.

- 스택과 마찬가지로 실행중에 동적으로 할당될 수 있으며, 리눅스에서는 스택 세그먼트와 반대 방향으로 자란다.

- c에서 malloc(), calloc()등을 호출해서 할당받는 메모리가 이 세그먼트에 위치한다.

- 일반적으로 읽기 권한과 쓰기 권한이 부여된다.

 

힙 세그먼트와 스택 세그먼트가 반대로 자라는 이유?

- 두 세그먼트가 동일한 방향으로 자라고, 연속된 메모리 주소에 각각 할당된다고 가정했을 때, 기존의 힙 세그먼트를 사용한 후 이를 확장하는 과정에서 스택 세그먼트와 충돌한다.

- 따라서 이를 해결하기 위해 스택을 메모리 끝에 위치시키고, 힙과 스택을 반대방향으로 자라게 한다.

 

int main(){

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

 

위의 코드를 보면...

- heap_data_ptr에 malloc()으로 동적할당한 영역의 주소를 대입한다.

- 이 영역에 값을 쓴다.

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


Note

 

세그먼트 역할 일반적인 권한 사용 예
코드 세그먼트 실행 가능한 코드가 저장된 영역 읽기, 실행 main()등의 함수 코드
데이터 세그먼트 초기화된 전역 변수 또는 상수가 위치하는 영역 읽기와 쓰기 또는 읽기 전용 초기화된 전역 변수, 전역 상수
BSS 세그먼트 초기화되지 않은 데이터가 위치하는 영역 읽기, 쓰기 초기화되지 않은 전역 변수
스택 세그먼트 임시 변수가 저장되는 영역 읽기, 쓰기 지역 변수, 함수의 인자 등
힙 세그먼트 실행중에 동적으로 사용되는 영역 읽기, 쓰기 malloc(), calloc() 등으로 할당 받은 메모리

 

728x90