PE파일이란?
리눅스 실행파일이 ELF인 것처럼 윈도우에서 사용하는 실행파일입니다.
PE파일의 종류
EXE, DLL, OBJ 등 윈도우OS에서 돌아가는 실행파일들이 있습니다.
PE파일의 구조
PE 파일은 아래 그림처럼 구조체 형식으로 저장되어 있습니다. 크게는 PE header와 body가 존재합니다.

메모장을 PEview로 열면 구조화되어 있는 걸 알 수 있습니다.
1. PE header
1. DOS Header

IMAGE_DOS_HEADER 구조체
- e_magic: DOS signature이다. PE 파일이라는 걸 나타낸다. MZ로 총 2바이트. 위 메모장에서도 data가 5A4D로 고정됨을 알 수 있습니다.
- e_lfanew: NT header가 시작되는 위치 offset입니다.
2. DOS stub(옵션)

DOS환경에서 실행되는 코드+데이터가 적혀있습니다. 위를 DOS환경에서 실행하면 “This program cannot be run in DOS mode”를 출력하고 종료합니다.
3. NT Header

IMAGE_NT_HEADERS 구조체는 파일 실행에 필요한 정보를 저장합니다(중요).
- signature: data를 읽으면(참고로 리틀엔디안) 00004550인데 이것은 PE를 뜻합니다.
- file header: 파일의 간략 정보를 나타냅니다. IMAGE_FILE_HEADER 구조체를 갖습니다.
| 주요 멤버명 | 의미 | notepad.exe |
|---|---|---|
| Machine | 실행되는 아키텍처 | 8664는 AMD64를 나타냅니다. |
| NumberOfSections | 파일에 존재하는 섹션 개수 | 7개입니다. |
| SizeOfOptionalHeader | Optional Header의 크기 | 0x00F0만큼 차지 |
| Characteristics | 속성 | bit OR계산을 해보면 0x0002 EXECUTABLE_IMAGE 0x0020 LARGE_ADDRESS_AWARE를 갖는 걸 알 수 있습니다. |
| TimeDateStamp | 파일의 생성 일시 | value를 보면 2007/06/20에 생성된 걸 알 수 있습니다. |
- optional header: 파일 실행에 필요한 정보들을 저장합니다.

위 파일 헤더에 적혀있던 것처럼 0x00F0만큼 갖습니다.
| 주요 멤버명 | 의미 |
|---|---|
| Magic | 32비트 or 64비트 |
| AddressOfEntryPoint | 로더가 실행을 시작할 주소 처음으로 실행될 코드 주소 |
| ImageBase | 메모리 상의 시작 주소 |
| SectionAlignment, FileAlignment | 섹션의 최소단위 |
| SizeOfImage | Image 전체 크기 |
| SizeOfHeader | PE header의 전체 크기 |
| Subsystem | 동작환경 정의 |
| NumberOfRvaAndSize | datadirectory배열의 개수 |
| DataDirectory | IMAGE_DATA_DIRECTORY 구조체의 배열 디렉토리별 각각의 정보 작성 |
4. Section Header

아래서 설명할 Section에 대한 header입니다. 메모리에 로드될 때 참조됩니다. 섹션의 자세한 .text, .rdata, .data등은 후술하고 주요 멤버만 짚고 넘어갑니다.
- VirtualSize: 메모리에서 섹션 크기
- VirtualAddress: 메모리에서 섹션의 시작주소(RVA라고 합니다)
- SizeOfRawData: 파일에서 섹션의 크기
- PointerToRawData: 파일에서 섹션의 Offset(길이)
- Characteristics: 섹션의 속성(위에서 본 멤버와 같은 원리)
2. PE body
body는 여러 개의 섹션으로 구성되어 있습니다.
| code 영역 | .text | 실행 코드를 담고 있는 섹션 |
|---|---|---|
| data 영역 | .data | 읽고 쓰기가 가능한 데이터 섹션 초기화된 전역변수와 정적 변수 등 위치 |
| .rdata | 읽기만 가능한 데이터 섹션 상수형 변수, 문자열 상수 등 위치 |
|
| .bss | 초기화되지 않은 전역변수 위치 | |
| import API | .idata | import할 DLL, API 정보 섹션 IAT 존재 |
| .didat | Delay-loading import DLL 정보 섹션 | |
| export API | .edata | export할 DLL 정보 섹션 |
| Resource | .rsrc | 리소스 관련 데이터 정보 섹션 |
| relocation | .reloc | 재배치 정보 섹션 |
| TLS | .tls | 스레드 지역 저장소 |
| Debugging | .debug$p | 미리 컴파일된 헤더가 있을 시 |
notepad.exe에는 위와 같은 섹션이 존재합니다. 중요한 code, data 영역 헤더를 살펴보겠습니다.

위는 .text의 섹션 헤더입니다. characteristics를 보면 실행, 읽기 권한이 있습니다.

왼쪽이 .data이고, 오른쪽이 .rdata입니다. 속성 차이를 보면 왼쪽은 읽기, 쓰기 권한이 있지만 오른쪽은 읽기 권한만 존재합니다. rdata에는 문자열 상수 등이 위치합니다.
가상 메모리 구조 분석
위 그림과 같이 메모리에 PE파일이 로드됩니다. 왼쪽 파일에서는 offset의 개념을 쓰지만 오른쪽 메모리에서는 RVA(relative virtual address)의 개념을 차용합니다. RVA는 시작 주소에 대한 상대적 VA를 말합니다. 이 RVA를 이용한 VA 식은 다음과 같습니다.
| VA = ImageBase + RVA |
image_file_header 뒷부분 IMAGE_OPTIONAL_HEADER 구조체에 메모리 로드 관련 멤버들이 존재합니다. 아까 위에서 설명했던 optional header에서는 주요 멤버만 설명하고 넘어갔는데 좀 더 자세히 설명해보겠습니다.

| 주요 멤버명 | 의미 |
|---|---|
| Magic | 실행되는 아키텍처 |
| SizeOfCode | .text의 크기 |
| AddressOfEntryPoint | 로더가 실행을 시작할 주소 처음으로 실행될 코드 주소 |
| ImageBase | 메모리 상의 시작 주소 |
| SectionAlignment, FileAlignment | 섹션의 최소단위 |
| SizeOfImage | Image 전체 크기 -> 위 alignment의 배수 |
| SizeOfHeader | PE header의 전체 크기 -> 위 alignment의 배수 |
| Subsystem | 동작환경 정의 |
| NumberOfRvaAndSize | datadirectory배열의 개수 |
| stack, heap 사이즈 관련 멤버 | 프로세스마다 가지는 자신만의 스택, 힙 그것들을 생성하기 위한 크기, 속성 지정 |
| DataDirectory | IMAGE_DATA_DIRECTORY 구조체의 배열 디렉토리별 각각의 정보 작성 |
각 섹션별 RVA와 size가 정의되어 있습니다.