컴퓨터 = CPU + Memory
CPU
CPU의 동작과 메모리 사이에 밀접한 연관이 있음을 알 수 있다.
즉, 공격자가 메모리를 악의적으로 조작할 수 있다면, 조작된 메모리 값에 의해 CPU도 잘못된 동작을 할 수 있다.
위의 경우를 메모리가 오염됐다고 표현하며, 이를 유발하는 취약점을 메모리 오염 취약점이라고 함.
시스템 해킹에서 많은 공격기법이 메모리 오염을 기반으로 함.
그리고, 이를 이해하기 위한 배경으로 리눅스 메모리 구조에 대해 오늘 배운다.
목표
1. 프로세스 가상메모리의 각 구역이 어떤 정보를 담고 있는지 이해
2. 1을 통해 프로세스 메모리의 전체 구조 알기
Segment : 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것(
( 리눅스에서는 프로세스의 메모리를 크게 5가지의 Segment로 구분 )
간단히 말해서, Segment = 메모리 영역
종류
장점
운영체제가 메모리를 용도별로 나누면, 각 용도에 맞게 적절한 권한을 부여할 수 있다.
권한 : 읽기, 쓰기, 실행
CPU는 메모리에 대한 권한이 부여된 행위만 가능
'실행 중인 프로세스의 메모리가 5개의 영역으로 구분된다.'
실행 가능한 기계 코드가 위치하는 영역(=Text Segment)
권한
읽기 및 실행 => 프로그램이 동작하려면 코드를 실행할 수 있어야하기 때문
쓰기 권한이 있다면?
공격자가 악의적인 코드 삽입하기 쉬움.
그래서 대부분의 현대 운영체제는 Code Segment에 쓰기 권한 부여 X
예시
int main() { return 31337; }
main함수가 컴파일 되면 기계 코드로 변환. 이 기계 코드가 Code Segment에 위치
컴파일 시점에 값이 정해진 전역 변수 및 전역 상수
권한
읽기 => CPU가 이 Segment의 Data를 읽을 수 있어야하기 때문
쓰기
예시
int data_num = 31337; // data
char dta_rwstr[] = "writable_data"; // data
const char data_rostt[] = "readonly_data"; // rodata
char *str_ptr = "readonly"; // str_ptr은 data, 문자열은 rodata
int main(){ ... }
str_ptr : "readonly" 라는 문자열을 가리킴. 이 문자열은 상수 문자열 로 취급
따라서 문자열 "readonly" 는 rodata에 위치하며,
이를 가리키는 str_ptr 은 전역 변수로서 data에 위치
컴파일 시점에 값이 정해지지 않은 전역 변수가 위치
개발자가 선언만 하고 초기화하지 않은 전역변수 등이 포함됨.
BSS Segment 는 프로그램이 시작될 때, 모두 0으로 값이 초기화 됨.
권한
읽기 및 쓰기
int bss_data;
int main() {
printf("%d\n", bss_data);
return 0;
}
bss_data는 초기화되지 않은 전역 변수이기 때문에 0이며, bss segment에 위치
포르세스의 스택이 위치하는 영역
함수의 인자나 지역 변수와 같은 임시 변수들이 실행중에 이곳에 저장.
단위
Stack Frame : 함수가 호출될 때 생성되고, 반환될 때 해제
프로그램의 전체 실행 흐름은 사용자의 입력을 비롯한 여러 요인에 영향을 받음.
따라서, 어떤 프로세스가 실행될 때, 이 프로세스가 얼만큼의 Stack Frame을 사용하게 될지 예상하는 것은 일반적으로 불가능하다.
( 아래의 예시에서 choice의 값에 따라 call_true 혹은 call_false가 결정됨 )
그래서 운영체제는 프로세스 시작 시 작은 크기의 Stack Segment 를 먼저 할당.
부족해질 때 마다 이를 확장
Stack은 확장될 때, 기존 주소보다 낮은 주소로 확장
권한
읽기와 쓰기
예시
void func() {
int choice = 0;
scanf("%d", &choice);
if(choice)
call_true();
else
call_false();
return 0;
}
위 코드에서 choice가 스택에 저장.
Heap Data 가 위치하는 Segment. Stack 과 마찬가지로 실행중에 동적으로 할당될 수 있음
C언어에서 malloc(), calloc() 등을 호출해서 할당받는 메모리 = Heap Segment에 위치.
권한
읽기와 쓰기
int main(){
int *heap_data_ptr = malloc(sizeof(*heap_data_ptr)); // 동적 할당한 힙 영역의 주소를 가리킴
*heap_data_ptr = 31337; // 힙 영역에 값을 씀
printf("%d\n", *heap_data_ptr); // 힙 영역의 값을 사용함
return 0;
}
Heap Segment 와 Stack Segment 가 반대 방향으로 자라는 이유는?
- Heap 과 Stack 은 메모리를 최대한 자유롭게 사용할 수 있으며, 충돌 문제로부터 비교적 자유로움.
Segment | 역할 | 일반적인 권한 | Example | 비고 |
---|---|---|---|---|
Code | 실행 가능한 코드가 저장된 영역 | 읽기, 실행 | main()등의 함수 코드 | |
Data | 초기화 o 전역 변수 or 상수가 위치 | 읽기와 쓰기 or 읽기 전용 | 초기화된 전역 변수, 전역 상수 | Data & rodata |
BSS | 초기화 x 데이터가 위치 | 읽기, 쓰기 | 초기화되지 않은 전역 변수 | |
Stack | 임시 변수가 저장 | 읽기, 쓰기 | 지역 변수, 함수의 인자 등 | |
Heap | 실행 중 동적으로 사용되는 영역 | 읽기, 쓰기 | malloc(), calloc()로 할당받은 메모리 |