반응형
강의 내용
- 서론
- 세그먼트
- 코드 세그먼트
- 데이터 세그먼트
- BSS 세그먼트
- 스택 세그먼트
- 힙 세그먼트
서론
- 메모리 오염(Memory Corruption)
- cpu의 동작과 메모리 사이에 밀접한 연관이 있음을 의미한다.
만약 공격자가 메모리를 조작할 수 있다면 조작된 메모리 값에 의해 CPU도 잘못된 동작을 할 수 있다. - Stack Buffer Overlow, Format String Bug, Use After Free, Double Free Bug
를 배우고 리눅스 메모리 구조(Memory Layout)에 대해 배운다.
- cpu의 동작과 메모리 사이에 밀접한 연관이 있음을 의미한다.
세그먼트
- 세그먼트(Segment)
- 리눅스에서 프로세스의 메모리를 크게 5가지의 세그먼트(Segment)로 구분한다.
- 적재되는 데이터의 용도별로 메모리의 구획은 나눈 것,
(코드 세그먼트, 데이터 세그먼트, BSS 세그먼트, 힙 세그먼트, 그리고 스텍 세그먼트)
- 메모리를 용도별로 나누었을때 장점
- 각 용도에 맞게 적절한 권한을 부여할 수 있다.
- 읽기, 쓰기, 실행 이있다. CPU는 메모리에 대해 권한이 부여된 행위만 할 수 있다.
- 예시
- 데이터 세그먼트에는 프로그램이 실행되면서 사용하는 데이터가 적재된다.
- CPU는 이곳의 데이터를 읽을 수 있어야 하며, 그래서 읽기 권한이 부여된다. 단 실행 대상이 아니끼 떄문에 실행 권한은 부여되지 않는다.
코드 세그먼트
- 코드 세그먼트(Code Segment)
- 실행 가능한 기계 코드가 위치하는 영역 (텍스트 세그먼트(Text Segment)라고도 불린다.)
- 프로그램이 동작하기 위해 코드를 실행할 수 있어야 하기 떄문에, 읽기, 쓰기 권한이 부여된다.
반면에 쓰기 권한이 있으면 공격자가 악성코드를 삽입하기 쉽기 때문에 , 쓰기 권한을 제거한다.
int main() { return 31337; }
//정수 31337을 반환하는 main함수가 컴파일 되면 554889e5b8697a00005dc3라는 기계코드로 변환된다.
//이 기계 코드가 코드 세그먼트에 위치한다.
데이터 세그먼트
- 데이터 세그먼트(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
// str_ptr은 전역변수, 문자열은 상수 문자열로 취급됨
int main() { ... }
BSS 세그먼트
- BSS 세그먼트(BSS Segment, Block Started By Symbol Segment)
- 컴파일 시점에 값이 정해지지 않은 전역 변수가 위차한다.
(선언만 하고 초기화 하지 않은 전역변수가 포함) - 프로그램이 시작될때, 모두 0으로 초기화 된다.
(c코드를 작성할 떄, 초기화 되지 않은 전역변수값은 0이된다.) - 읽기, 쓰기 권한이 부여된다.
- 컴파일 시점에 값이 정해지지 않은 전역 변수가 위차한다.
int bss_data; // BSS 세그먼트에 위치
int main() {
printf("%d\n", bss_data); // 0
return 0;
}
스택 세그먼트
- 스택 세그먼트(Stack Segment)
- 프로세스의 스택이 위치하는 영역이다.
- 함수의 인자나 지역변수와 같은 임시 변수들이 실행중에 저장된다.
- 스택 프레임(Stack Frame)
- 함수가 호출될때 생성되고, 반환될떄 해제된다.
- 프로그래므의 전체 실행 흐름은 사용자의 입력을 비롯한 여러 요인에 영향을 받는다.
👇아래 코드에서 유저가 입력한 choice에 따라 call_true()가 호출될 수도, call_false()가 호출될 수도 있다.
void func() {
int choice = 0;
scanf("%d", &choice);
if (choice)
call_true();
else
call_false();
return 0;
}
// 지역변수 choice가 스태겡 저장되게 됩니다.
- 추가설명
- 프로세스가 얼마 만큼의 스택 프레임을 사용하게 될지를 미리 계산하는 것은 일반적으로 불가능하다.
따라서 운영체제는 작은 크기의 스택 세그먼트를 먼저 할당해주고, 부족해 질 떄마다 이를 확장해준다. - 스택에서 '아래로 자란다'란다 라는 표현을 사용하느데, 스텍이 확장될 때, 기존 주소보다 낮은 주소로 확장되기 떄문이다.
- CPU가 자유롭게 값을 일고 쓸 수 있어야 하므로, 읽기와 쓰기 권한이 부여된다.
- 프로세스가 얼마 만큼의 스택 프레임을 사용하게 될지를 미리 계산하는 것은 일반적으로 불가능하다.
힙 세그먼트
- 힙 세그먼트(Heap Segment)
- 힙 데이터가 위치하는 세그먼트 이다.
(실행 중에 동적으로 할당될 수 있다., 스택 세그먼트와 반대 방향으로 자란다.) - malloc(), calloc()등을 호출해서 할당받는 메모리가 위치한다.(읽기와 쓰기 권한이 부여된다.)
- 힙 데이터가 위치하는 세그먼트 이다.
👇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;
}
// heap_data_ptr은 지역변수이므로 스택에 위치하며, malloc으로 할당받은 힙 세그먼트의 주소를 가리킨다.
💡힙과 스택 세그먼트가 자라는 방향이 반대인 이유?
- 두 세그먼트가 동일한 방향으로 자라며, 연속된 메모리 주소에 각각 할당된다고 가정해본다. 이 경우, 기존에 힙 세그먼트를 모두 사용하면, 이를 확장하는 과정에서 스택 세그먼트와 충돌하게 된다.
- 이를 쉽게 해결하기 위해 리눅스는 스택을 메모리의 끝에 위치시키고, 힙과 스택을 반대로 자라게한다. 이렇게 하면 힙과 스택은 메모리를 최대한 자유롭게 사용할 수 있으며, 충돌 문제로 부터도 비교적 자유롭게 된다.
결론
세그먼트 | 역할 | 일반적인 권한 | 사용예 |
코드 세그먼트 | 실행 가능한 코드가 저장된 영역 | 읽기, 실행 | main()등의 함수 코드 |
데이터 세그먼트 | 초기화된 전역 변수 또는 상수가 위치하는 영역 | 일릭, 쓰기 또는 읽기 | 초기화된 전역 변수, 전역 상수 |
BSS 세그먼트 | 초기화 되지 않은 데이터 위차하는 영역 | 읽기, 쓰기 | 최기화 되지 않은 전역변수 |
스택 세그먼트 | 임시 변수가 저장되는 영역 | 읽기, 쓰기 | 지역변수, 함수의 인자 |
힙 세그먼트 | 실행중에 동적으로 사용되는 영역 | 읽기, 쓰기 | malloc(), calloc()등으로 할당 받은 메모리 |
끝
반응형
'hacking > pwnable' 카테고리의 다른 글
[Dream hack] Quiz: Linux Memory Layout-2 (2) | 2024.05.01 |
---|---|
[Dream hack] Quiz: Linux Memory Layout-1 (0) | 2024.04.30 |
[Dream hack] Quiz: Computer Architecture-5 (0) | 2024.04.25 |
[Dream hack] Quiz: Computer Architecture-4 (2) | 2024.04.25 |
[Dream hack] Quiz: Computer Architecture-3 (0) | 2024.04.25 |