- Background: Windows Memory Layout2025년 03월 18일 20시 46분 49초에 업로드 된 글입니다.작성자: SONOTREE
01. 서론 ★
메모리 레이아웃(Memory Layout)이란 프로세스 가상 메모리(Virtual Memory)의 구성을 말한다. 프로그램을 실행하면 운영체제는 프로세스에게 사용 가능한 메모리 공간을 할당해 준다. 컴퓨터 과학에서는 이 공간을 가상 메모리라고 부른다.
OS는 프로그램의 정보를 참조하여, 프로그램에 저장된 데이터가 적절한 영역에 저장되게 한다. 프로세스가 사용할 가상 메모리를 용도별로 구획하고, 프로세스가 사용하는 데이터를 적절한 구획에 저장한다. 유사한 데이터를 모아 놓기 때문에 OS는 각 구획에 적절한 권한을 부여할 수 있으며, 개발자는 프로세스의 메모리를 더 직관적으로 이해할 수 있게 된다.
소프트웨어 리버스 엔지니어링의 핵심은, 바이너리(프로그램)를 분석하여 바이너리의 동작을 이해하는 것이다. 그런데 Background: Computer Architecture에서 공부했듯, 바이너리의 동작은 메모리와 굉장히 밀접한 관련이 있다. 따라서 바이너리의 동작을 자세히 이해하기 위해서는 바이너리가 상호작용하는 메모리에 대한 이해가 필요하다.
이 강의에서는 윈도우 프로세스의 메모리 레이아웃에 대한 기초적인 내용을 배울 것이다.
02. 섹션
윈도우의 PE 파일은 PE 헤더 + 1개 이상의 섹션으로 구성되어 있다. 여기서 섹션이란, 유사한 용도로 사용되는 데이터가 모여있는 영역을 의미한다. 예를 들어 .text 섹션에는 PE의 코드가 적혀있고, .data에는 PE가 실행중에 참조하는 데이터가 적혀있다.
▼섹션에 대한 정보는 PE 헤더에 적혀있다. PE 헤더에 저장되는 섹션과 관련된 데이터 중, 중요한 것은 다음과 같다
- 섹션의 이름
- 섹션의 크기
- 섹션이 로드될 주소의 오프셋(값)
- 섹션의 속성과 권한
윈도우는 PE를 실행할 때, (헤더로부터)이 정보를 참조하여 PE의 각 세션들을 가상 메모리의 적절한 세그먼트에 매핑한다. PE에 필수로 존재해야하는 섹션이 정해진 것은 아니지만, .text, .data, .rdata 섹션이 일반적으로 사용된다. 아래에서 각 섹션의 역할을 살펴보도록 하자.
02.1. .text 섹션
.text 섹션은 실행 가능한 기계 코드가 위치하는 영역이다.
프로그램이 동작하려면 코드를 실행할 수 있어야 하므로, 이 세그먼트에는 읽기 권한과 실행 권한이 부여된다. 하지만 쓰기 권한이 있으면 공격자가 악의적인 코드를 삽입하기가 쉬워지므로, 대부분의 현대 OS는 이 세그먼트에 쓰기 권한을 제거한다.
아래에서 정수 31337을 반환하는 main 함수가 컴파일되면 554889e5b8697a00005dc3 라는 기계 코드로 변환되는데, 이 기계 코드가 코드 세그먼트에 위치하게 된다.
int main() { return 31337; }
02.2. .data 섹션
.data 섹션에는 컴파일 시점에 값이 정해진 전역 변수들이 위치한다. CPU가 이 섹션의 데이터를 읽고 쓸 수 있어야 하므로, 읽기/쓰기 권한이 부여된다. 아래는 .data 섹션에 포함되는 여러 데이터의 유형들이다.
int data_num = 31337; char data_rwstr[] = "writable_data"; // data int main() { ... }
02.3. .rdata 섹션
.rdata 섹션에는 컴파일 시점에 값이 정해진 전역 변수 + 참조할 DDL(Dynamic Link Library) 및 외부 함수들의 정보가 저장된다. CPU가 이 섹션의 데이터를 읽을 수 있어야 하므로 읽기 권한이 부여되지만, 쓰기 권한은 주어지지 않는다.
아래는 .rdata 섹션에 포함되는 데이터의 여러 유형이다. 주의 깊게 살펴봐야할 변수는 str_ptr이다. str_ptr은 "readonly"라는 문자열을 가리키고 있는데, str_ptr은 전역 변수로서 .data에 위치하지만, "readonly"는 상수 문자열로 취급되어 .rdata에 위치한다.
const char data_rostr[] = "readonly_data"; char *str_ptr = "readonly"; // str_ptr은 .data, 문자열은 .rdata int main() { ... }
(과거에는 참조할 DLL과 외부 함수들의 정보를 .idata에 저장하였으나, 최근에는 대부분 .rdata에 저장한다)
03. 섹션이 아닌 메모리
윈도우의 가상 메모리 공간에는 섹션만 로드되는 것이 아니다. 프로그램 실행에 있어 필요한 스택과 힙 역시 가상 메모리 공간에 적재된다.
03.1. 스택
윈도우즈 프로세스의 각 쓰레드(프로세스 내에서 실제로 작업을 수행하는 주체)는 자신만의 스택 공간을 가지고 있다. 보통 지역 변수나 함수의 리턴 주소가 저장된다. 이 영역은 자유롭게 읽고 쓸 수 있어야 하기 때문에 읽기/쓰기 권한이 부여된다. 참고로 스택에서 "아래로 자란다"라는 표현을 종종 사용하는데, 이는 스택이 확장될 때 기존 주소보다 낮은 주소로 확장되기 때문이다.
아래의 코드에서 지역변수 choice가 스택에 저장되게 된다. (스택, 힙에 대한 내용은 여기를 참고)
void func() { int choice = 0; scanf("%d", &choice); if (choice) call_true(); else call_false(); return 0; }
03.2. 힙
힙은 프로그램이 여러 용도로 사용하기 위해 할당받는 공간이다. 따라서 모든 종류의 데이터가 저장될 수 있다. 스택과 다른 점은 비교적 스택보다 큰 데이터도 저장할 수 있고, 전역적으로 접근이 가능하도록 설계되었다는 점이다. 또한 실행중 동적으로 할당받는 점 역시 다르다.
권한은 보통 데이터를 읽고 쓰기만 하기 때문에 읽기/쓰기 권한만을 가지나, 상황에 따라 실행 권한을 가지는 경우도 존재한다.
▼아래 예제 코드는 heap_data_ptr에 malloc()로 동적 할당한 영역의 주소를 대입하고, 이 영역에 값을 쓴다. heap_data_ptr은 지역변수이므로 스택에 위치하며, malloc으로 할당받은 힙 세그먼트의 주소를 가리킨다. 정리해보면 다음과 같다.
- heap_data_ptr은 지역변수이며 스택 영역에 위치한다
- malloc()로 동적 할당한 메모리의 주소를 heap_data_ptr에 저장한다
- heap_data_ptr이 가리키는 메모리는 힙 영역에 존재한다
- 즉 , heap_data_ptr은 힙 영역의 메모리를 가리키는 포인터가 된다
int main() { int *heap_data_ptr = malloc(sizeof(*heap_data_ptr)); // 동적 할당한 힙 영역의 주소를 가리킴 *heap_data_ptr = 31337; // 힙 영역에 값을 씀 printf("%d\n", *heap_data_ptr); // 힙 영역의 값을 사용함 return 0; }
'Dreamhack > Reverse Engineering' 카테고리의 다른 글
x86 Assembly: Essential Part(2) [★] (0) 2025.03.23 x86 Assembly: Essential Part(1) (0) 2025.03.22 Quiz: Computer Architecture [★] (0) 2025.03.18 Background: Static Analysis vs. Dynamic Analysis (0) 2025.03.18 Background: Binary (0) 2025.03.18 다음글이 없습니다.이전글이 없습니다.댓글