
드디어 PE(Portable Executable) 파일 포맷 포스팅을 하고자한다.
내용이 길것으로 예상되나 중요한 파트이므로 천천히 짚고 넘어가자.
1. PE 포맷?
PE 포맷이란 Portable Executable 약자로 윈도우 운영체제에서 사용되는 실행 가능한 파일 형태를 말한다.
아래서 설명할 PE 파일은 말그대로 PE 포맷 구조를 가지는 파일이다.
대표적인 포맷인 만큼 다양한 분석툴에서 PE파일을 분석할 수 있다.
PE Viewer가 대표적이며 본 포스팅에선 개념 이해를 위해 HxD를 이용해 설명하도록 하겠다.
2. PE 파일 종류
PE 포맷을 가진다고 무조건 실행하는 계열의 확장자만 해당되는 것이 아니다.
다음과 같은 다양한 파일 포맷이 PE 구조를 가진다. 대표적으로 .exe와 .dll이 존재한다.
- 실행 계열 : EXE, SCR
- 드라이버 계열 : SYS, VXD
- 라이브러리 계열 : DLL, OCX, CPL, DRV
- 오브젝트 파일 계열 : OBJ
3. PE 파일 구조
PE 파일은 다음과 같이 구성되어있다.
- Header + Section
Header란 실행 파일의 성격과 특징을 나타내며(프로그램 구동 정보 등등),
Section은 구체적이고 세부적인 기능을 나타낸다(코드, 전역변수 등등) .
위 구조를 그림으로 좀더 자세히 표현하자면 아래와 같다.

각 영역에 대해 살펴보자.
4. PE 파일 구조 - DOS 영역
MS에서 DOS 파일에 대한 하위 호환성을 고려하여 PE 파일 포맷을 만들었다.
DOS 영역의 두가지 영역을 살펴보자.
1) DOS Header
이제부터 몇몇 영역을 아래와 같이 소스코드로 나타낼 건데, 이는 MS에서 제공하는 winnt.h에서 가져온 내용이다.
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // Magic number > DOS Signature : 4D5A("MZ")
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; // Offset of NT Header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
DOS Header 영역으로 중요한 정보는 두가지다.
첫번째 e_magic 값과 e_lfanew 값이다.
- e_magic : DOS Signature 값으로 0x4D5A("MZ")라는 값을 가진다.
- e_lfanew : NT Header의 오프셋 주소를 가진다. DOS Stub 영역을 점프하는 역할을 한다.
2) DOS Stub
에러메세지를 출력하는 부분이다.
가변적인 영역으로 옵션에 의해 존재여부를 결정한다.
없어도 파일은 잘 실행된다.
5. PE 파일 구조 - NT Header 영역
NT Header 영역은 3가지 영역이 존재한다.
1) PE File Signature
NT Header 시작부분에 위치하며 PE File Signature 값으로 0x5045("PE")라는 값을 가진다.
2) PE File Header(IMAGE_FILE_HEADER)
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
이 부분에서 중요한 정보는 Machine, NumberOfSections, SizeOfOptionalHeader, Characteristics로 4가지 이다.
- Machine : CPU별로 고유한 값을 가진다. 0x014C는 IA-32, 0x0200은 IA-64와 같은 값을 가지고 있다.
- NumberOfSections : 섹션의 갯수를 나타내며, 1개 이상의 값을 가진다.
*만약 정의된 섹션수 > 실제 섹션 : error
*만약 정의된 섹션수 < 실제 섹션 : 정의된 섹션수만큼 인식 - SizeOfOptionalHeader : 마지막 멤버 Optional Header32 구조체의 크기를 나타낸다.
- Characteristics : 파일의 속성을 나타낸다. 실행 가능한 파일인지, dll 파일인지와 같은 정보 등을 나타낸다.
3) PE File Optional Header(IMAGE_OPTIONAL_HEADER)
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
PE Header 중 가장 크기가 큰 구조체다.
실행에 필수적인 값들이 존재하므로 잘못 값들이 세팅되면 프로그램이 실행되지 않는다.
모두 중요하지만 특히 중요한 정보들에 한하여 설명하겠다.
- Magic : IMAGE_OPTIONAL_HEADER32이면 10Bh, IMAGE_OPTIONAL_HEADER64면 20Bh
- AddressOfEntryPoint : EP(Entry Point)의 RVA(Relative Virtual Address)값을 가짐
- ImageBase : 프로세스 가상 메모리(32bit) : 0 - FFFFFFFFh, 로딩(매핑)된는 시작 주소를 나타냄
- SectionAlignment, FileAlignment : PE 파일은 섹션으로 나뉨, SectionAlignment는 메모리에서 섹션 최소단위, FileAlignment는 파일에서 섹션의 최소단위 나타냄
- SizeOfImage : PE 파일 메모리 로딩 시 가상 메모리에서 PE Image가 차지하는 크기
- SizeOfHeader : PE Header 전체 크기
- Subsystem : 1이면 Driver 파일(sys, vxd), 2면 GUI 파일, 3이면 CUI 파일
- NumberOfRvaAndSizes : 마지막 멤머 DataDirectory 배열 크기
- DataDirectory : IMAGE_DATA_DIRECTORY 구조체의 배열
6. PE 파일 구조 - Section Header 영역
Section Header 영역은 Section에 대한 구성 정보를 담고 있다.
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
5가지 정보에 대해 설명하겠다.
- VirtualSize : 메모리에서 섹션이 차지하는 크기
- VirtualAddress : 메모리에서 섹션 시작 주소(RVA), SectionAlignment에 맞춰 결정
- SizeOfRawData : 파일에서 섹션이 차지하는 크기
- PointerToRawData : 파일에서 섹션 시작 위치, FileAlignment에 맞춰 결정
- Characteristics : 섹션의 속성
*RVA는 메모리에서 주소를 나타내며, RAW는 파일에서의 주소를 나타낸다.
*RVA를 RAW로 공식을 적용하여 계산할 수 있다.
- RAW = RVA - VirtualAddress + PointerToRawData
- 만약 RVA가 101400h, VirtualAddress가 101000h, PointerToRawData가 100000h이면 RAW 값은 100400h
7. PE 파일 구조 - Section 영역
Section 영역은 PE 파일의 실제 데이터를 담고 있다.
대표적인 몇몇 영역을 설명하겠다.
- .text : 실행코드, 형식 제한 없음, 읽기 권한 지님, 리틀엔디안
- .data : 초기화된 각종 변수, 형식 제한 없음, 읽기/쓰기 권한 지님
- .rdata : 읽기 전용
- .idata : import 섹션으로 DLL/함수 정보
- .edata : export 섹션으로 - 본인함수 정보
- .rsrc : 리소스, 비실행, 읽기 권한 지님, 파일 버전이나 아이콘 등 기타 데이터 정보
...
이외에도 다양한 영역이 존재한다. 여기까지 이론을 마치고 실습을 해보겠다.
8. notepad.exe 분석
HxD를 통해 위와 같은 구조로 notepad.exe를 분석할 수 있다.
NT Header의 PE File Header 부분까지 살펴보자.

1. DOS Header의 DOS Signature 값인 4D 5A를 확인할 수 있음
2. NT Header Offset을 나타내는 lfanew 값인 F8 00 00 00를 확인하여 F8 주소로 점프
3. 50 45라는 NT Header 시작부분인 PE File Signature를 볼 수 있음

4. NT File Header 부분 Magic값인 64 86를 통해 AMD64 CPU체제 확인 가능
5. 이어서 NumberOfSections이 07 00을 통해 7개의 섹션을 가진 것 확인
6. SizeOfOptionalHeader을 통해 IMAGE_OPTIONAL_HEADER32가 F0의 크기를 가진 것 확인
7. Charateristics값인 22 00을 통해 실행가능파일부터 다양한 정보 확인가능
각 헥스값에 대한 의미는 추가적인 검색을 통해 의미를 파악할 수 있으며
위와 같이 분석을 진행해 나가면 된다.
물론 실전에선 PE Viewer와 같은 전용 분석툴을 통해 분석하면
쉽게 구조를 파싱하여 확인 가능하므로 참고하자.
여기까지 PE 공부를 마친다. 다음시간엔 OLE 구조를 공부해보겠다.
'My > Study' 카테고리의 다른 글
Clean Code - 1 (1) | 2021.07.12 |
---|---|
[Forensic] NTFS 파일 시스템에서 파일 복구해보기 (2) | 2020.10.16 |
[Network] WPA2 Cracking with Hashcat (0) | 2020.10.14 |
[네트워크] Suricata(수리카타) 공부 (0) | 2020.07.23 |
[MYSQL] JOIN 조인(INNER, OUTER, CARTESIAN, SELF)공부 (0) | 2020.04.11 |