Background: Linux Memory Layout
# 서론
- 컴퓨터는 크게 CPU와 메모리로 구성되어 있음
- CPU는 실행할 명령어와 명령어 처리에 필요한 데이터를 메모리에서 읽고, Instruction Set Architecture(ISA)에 따라 이를 처리함.
그리고 연산의 결과를 다시 메모리에 적재
> 이는 CPU의 동작과 메모리 사이에 밀접한 연관이 있음을 의미
- 만약 공격자가 메모리를 악의적으로 조작할 수 있다면 조작된 메모리 값에 의해 CPU도 잘못된 동작을 할 수 있음
- 이것을 메모리가 오염되었다고 표현하며, 이를 유발하는 취약점을 메모리 오염(Memory Corruption) 취약점이라고 부름
- 시스템 해킹에 다양한 기법들이 있지만, 많은 공격 기법이 Memory Corruption을 기반으로 하고 있음
- 개요 ✔
- 세그먼트란
- 코드 세그먼트
- 데이터 세그먼트
- BSS 세그먼트
- 스택 세그먼트
- 힙 세그먼트
# 리눅스 프로세스의 메모리 구조
1. 세그먼트
- 리눅스에서는 프로세스의 메모리를 크게 5가지의 세그먼트(Segment)로 구분
- 세그먼트란 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것인데, 크게 코드 세그먼트, 데이터 세그먼트, BSS 세그먼트, 힙 세그먼트, 그리고 스택 세그먼트로 구분
- 운영체제가 메모리를 용도별로 나누면, 각 용도에 맞게 적절한 권한을 부여할 수 있다는 장점이 있음
- 권한은 읽기, 쓰기, 그리고 실행이 존재하며, CPU는 메모리에 대해 권한이 부여된 행위만 할 수 있음
ex) 데이터 세그먼트에는 프로그램이 실행되면서 사용하는 데이터가 적재 됨
CPU는 이곳의 데이터를 읽을 수 있어야 하며, 따라서 이 영역에는 읽기 권한이 부여됨
반면 이 영역의 데이터는 실행 대상이 아니므로 실행 권한은 부여되지 않음
2. 코드 세그먼트
- 코드 세그먼트(Code Segment)는 실행 가능한 기계 코드가 위치하는 여역으로 텍스트 세그먼트(Text Segment)라고도 불림
- 프로그램이 동작하려면 코드를 실행할 수 있어야 하므로 이 세그먼트에는 읽기 권한과 실행 권한이 부여됨
- 반면 쓰기 권한이 있으면 공격자가 악의적인 코드를 삽입하기가 쉬워지므로, 대부분의 현대 운영체제는 이 세그먼트에 쓰기 권한을 제거함
int main() { return 31337; }
정수 31337을 반환하는 main 함수가 컴파일되면
554889e5b8697a00005dc3이라는 기계 코드로 변환되는데, 이 기계 코드가 코드 세그먼트에 위치하게 됨
3. 데이터 세그먼트
- 데이터 세그먼트(Data Segment): 컴파일 시점에 값이 정해진 전역 변수 및 전역 상수들이 위치
- CPU가 이 세그먼트의 데이터를 읽을 수 있어야 하므로, 읽기 권한이 부여됨
- 데이터 세그먼트는 쓰기가 가능한 세그먼트와 쓰기가 불가능한 세그먼트로 다시 분류됨
> data 세그먼트: 쓰기가 가능한 세그먼트는 전역 변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터들이 위치
> rodata(read-only 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() { ... }
위는 데이터 세그먼트에 포함되는 여러 데이터의 유형임
주의 깊게 살펴봐야 할 변수는 str_ptr임
이는 "readonly"라는 문자열을 가리키고 있는데, 이 문자열은 상수 문자열로 취급되어 rodata에 위치하며,
이를 가리키는 str_ptr은 전역 변수로서 data에 위치
4. BSS 세그먼트
- BSS 세그먼트(BSS Segment, Block Started By Symbol Segment): 컴파일 시점에 값이 정해지지 않은 전역 변수가 위치하는 메모리 영역
> 여기에는 개발자가 선언만 하고 초기화하지 않은 전역변수 등이 포함됨
- 이 세그먼트의 메모리 영역은 프로그램이 시작될 때, 모두 0으로 값이 초기화 됨
> 이런 특성 때문에 C 코드를 작성할 때, 초기화되지 않은 전역 변수의 값은 0이 됨
- 해당 세그먼트에는 읽기 권한 및 쓰기 권한이 부여됨
int bss_data;
int main() {
printf("%d\n", bss_data); // 0
return 0;
}
초기화되지 않은 전역 변수인 bss_data가 BSS 세그먼트에 위치하게 됨
5. 스택 세그먼트
- 스택 세그먼트(Stack Segment): 프로세스의 스택이 위치하는 영역
- 함수의 인자나 지역 변수와 같이 임시 변수들이 실행 중에 여기에 저장됨
- 스택 세그먼트는 스택 프레임(Stack Frame)이라는 단위로 사용됨
- 스택 프레임은 함수가 호출될 때 생성되고, 반환될 때 해제됨
> 그런데 프로그램의 전체 실행 흐름은 사용자의 입력을 비롯한 여러 요인에 영향을 받음
ex) 아래 코드에서 유저가 입력한 choice에 따라 call_true()가 호출될 수도, call_false()가 호출될 수도 있음
- 따라서 어떤 프로세스가 실행될 때, 이 프로세스가 얼마 만큼의 스택 프레임을 사용하게 될 지를 미리 계산하는 것은 일반적으로 불가능함
- 그래서 운영체제는 프로세스를 시작할 때 작은 크기의 스택 세그먼트를 먼저 할당해주고, 부족해 질 때마다 이를 확장해줌
- 스택에 대해서 '아래로 자란다'라는 표현을 사용하는데, 이는 스택이 확장될 때 기존 주소보다 낮은 주소로 확장되기 때문
- 이 영역에는 CPU가 자유롭게 값을 읽고 쓸 수 있어야 하므로, 읽기와 쓰기 권한이 부여됨
void func() {
int choice = 0;
scanf("%d", &choice);
if (choice)
call_true();
else
call_false();
return 0;
}
위 코드에서는 지역변수 choice가 스택에 저장되게 됨
6. 힙 세그먼트
- 힙 세그먼트(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으로 할당받은 힙 세그먼트의 주소를 가리킴
💡 힙과 스택 세그먼트가 자라는 방향이 반대인 이유?
- 두 세그먼트가 동일한 방향으로 자라며, 연속된 메모리 주소에 각각 할당된다고 가정
- 이 경우, 기존의 힙 세그먼트를 모두 사용하고 나면, 이를 확장하는 과정에서 스택 세그먼트와 충돌하게 됨
- 이를 쉽게 해결하기 위해 리눅스는 스택을 메모리의 끝에 위치시키고, 힙과 스택을 반대로 자라게 함
- 이렇게 하면 힙과 스택은 메모리를 최대한 자유롭게 사용할 수 있으며, 충돌 문제로 부터도 비교적 자유롭게 됨
# 결론
세그먼트 | 역할 | 일반적인 권한 | 사용 예 |
코드 세그먼트 | 실행 가능한 코드가 저장된 영역 | 읽기, 실행 | main() 등의 함수 코드 |
데이터 세그먼트 | 초기화된 전역 변수 또는 상수가 위치하는 영역 | 읽기와 쓰기 또는 읽기 전용 | 초기화된 전역 변수, 전역 상수 |
BSS 세그먼트 | 초기화되지 않은 데이터가 위치하는 영역 | 읽기, 쓰기 | 초기화되지 않은 전역 변수 |
스택 세그먼트 | 임시 변수가 저장되는 영역 | 읽기, 쓰기 | 지역 변수, 함수의 인자 등 |
힙 세그먼트 | 실행 중에 동적으로 사용되는 영역 | 읽기, 쓰기 | malloc(), calloc() 등으로 할당 받은 메모리 |
# Quiz
풀이: 초기화되지 않은 영역은 BSS 세그먼트에 저장됨
풀이: 지역 변수와 같은 임시 변수는 스택 세그먼트에 저장됨
풀이: b가 "d_str"라는 문자열을 가리키고 있는데,
이 문자열은 상수 문자열로 취급되어 rodata에 위치하게 됨
풀이: malloc()으로 할당 받은 메모리를 가리키기 때문에 heap 세그먼트의 데이터를 가리킴
풀이: 전역 변수와 같이 프로그램 실행 중 값이 바뀔 수 있는 데이터는 데이터 세그먼트에 저장됨
풀이: 실행 중 값이 변경될 수 있는 전역 변수이므로 데이터 세그먼트에 위치
풀이: 실행 가능한 코드인 함수 코드가 저장되므로 코드 세그먼트에 위치
'Hacking Tech > System Hacking' 카테고리의 다른 글
[Dreamhack] Tool: pwntools (0) | 2022.03.28 |
---|---|
[Dreamhack] Tool: gdb (0) | 2022.03.27 |
[Dreamhack] x86 Assembly🤖: Essential Part(2) (0) | 2022.03.27 |
[Dreamhack] x86 Assembly🤖: Essential Part(1) (0) | 2022.03.26 |
[Dreamhack] Background: Computer Architecture (0) | 2022.03.25 |