Windows PE 구조
2022년 3월 18일
PE 구조 #
1. IMAGE_DOS_HEADER #
http://stixproject.github.io/data-model/1.2/WinExecutableFileObj/DOSHeaderType/
DOS와 호환을 위해 만든 헤더
1/*** WinNT.h ***/
2typedef struct _IMAGE_DOS_HEADER // DOS .EXE header
3{
4 WORD e_magic; // Magic number
5 WORD e_cblp; // Byte on last page of file
6 WORD e_cp; // Pages in file
7 WORD e_crlc; // Relocations
8 WORD e_cparhdr; // Size of header in paragraphs
9 WORD e_minalloc; // Minimum extra paragraphs needed
10 WORD e_maxalloc; // Maximum extra paragraphs needed
11 WORD e_ss; // Initial (relative) SS value
12 WORD e_sp; // Checksum
13 WORD e_ip; // Initital IP value
14 WORD e_cs; // Initial (relative) CS value
15 WORD e_lfarlc; // File address of relocation table
16 WORD e_ovno; // Overlay number
17 WORD e_res[4]; // Reserved words
18 WORD e_oemid; // OEM identifier (for e_oeminfo)
19 WORD e_oeminfo; // OEM information; e_oemid specific
20 WORD e_res2[10]; // Reserved words
21 LONG e_lfanew; // File address of new exe header
22} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
- Signature(e_magic) : 매직코드로 PE파일이면 무조건 MZ이다
- Bytes on Last Page of File(e_cblp) : 보통은 0x90인데, delphi로 컴파일된 경우 0x50 값을 갖게된다.
https://stackoverflow.com/questions/5531874/help-with-finding-hex-header-information-of-file - Offset to New EXE Header(e_lfanew) : IMAGE_NT_HEADER의 시작 오프셋값을 갖게된다.
2. MS-DOS Stub Program #
호환성을 위해 추가된 도스 실행코드이다. 실제로 도스에서 실행됐을때 이 문구가 출력된다.
3. IMAGE_NT_HEADERS #
https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_nt_headers32
1/*** 32bit ***/
2typedef struct _IMAGE_NT_HEADERS {
3 DWORD Signature;
4 IMAGE_FILE_HEADER FileHeader;
5 IMAGE_OPTIONAL_HEADER32 OptionalHeader;
6} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
7
8/*** 64bit는 OPTIONAL_HEADER가 64bit용인 것 빼고는 차이 없다. ***/
- Signature : PE 파일은 무조건 PE가 적혀있다.
3-1. IMAGE_FILE_HEADER #
https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_file_header
파일에 대한 기본적인 정보가 저장되어 있다.
1typedef struct _IMAGE_FILE_HEADER {
2 WORD Machine; // 파일이 동작하는 CPU 종류
3 WORD NumberOfSections; // 섹션의 수
4 DWORD TimeDateStamp; // 파일 생성 시간
5 DWORD PointerToSymbolTable;
6 DWORD NumberOfSymbols;
7 WORD SizeOfOptionalHeader; // OptionalHeader 크기
8 WORD Characteristics; // 파일의 형식
9} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
-
Machine : x86=0x14c, x64=0x200, AMD64=0x8664
-
TimeDateStamp : 링커에서 이미지를 만든 시간을 의미한다
-
PointerToSymbolTable, NumberOfSymbols : 옛날에 심볼 테이블이 파일 내에 포함되어있을때 사용하던 방식인데, 요즘 심볼은 크기가 너무 커져 별도로 생성되기 때문에 사용되지 않는 필드이다.
-
SizeOfOptionalHeader : 이후에 나오게될 OptionalHeader의 크기를 저장한다. 32bit=0xE0, 64bit=0xF0, OBJ=0X00
-
Characteristics : 현재 파일의 형식을 알려준다.
- DLL : 0x2000
- execute : 0x0002 (dll도 여기에 포함된다)
3-2. IMAGE_OPTIONAL_HEADER32 #
https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32
파일 실행에 관한 중요한 정보가 저장된다.
64bit 헤더는 BaseOfData가 삭제되고 일부 4byte 필드가 8byte로 변경되며 총 0x10byte 만큼 커졌다.
1
2typedef struct _IMAGE_OPTIONAL_HEADER {
3 WORD Magic;
4 BYTE MajorLinkerVersion;
5 BYTE MinorLinkerVersion;
6 DWORD SizeOfCode;
7 DWORD SizeOfInitializedData;
8 DWORD SizeOfUninitializedData;
9 DWORD AddressOfEntryPoint;
10 DWORD BaseOfCode;
11 DWORD BaseOfData; // 64bit : 삭제
12 DWORD ImageBase; // 64bit : ULONGLONG 타입 4byte->8byte
13 DWORD SectionAlignment;
14 DWORD FileAlignment;
15 WORD MajorOperatingSystemVersion;
16 WORD MinorOperatingSystemVersion;
17 WORD MajorImageVersion;
18 WORD MinorImageVersion;
19 WORD MajorSubsystemVersion;
20 WORD MinorSubsystemVersion;
21 DWORD Win32VersionValue;
22 DWORD SizeOfImage;
23 DWORD SizeOfHeaders;
24 DWORD CheckSum;
25 WORD Subsystem;
26 WORD DllCharacteristics;
27 DWORD SizeOfStackReserve; // 64bit : ULONGLONG
28 DWORD SizeOfStackCommit; // 64bit : ULONGLONG
29 DWORD SizeOfHeapReserve; // 64bit : ULONGLONG
30 DWORD SizeOfHeapCommit; // 64bit : ULONGLONG
31 DWORD LoaderFlags;
32 DWORD NumberOfRvaAndSizes;
33 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
34} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
35
36typedef struct _IMAGE_DATA_DIRECTORY {
37 DWORD VirtualAddress;
38 DWORD Size;
39} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
- Magic : OPTIONAL_HEADER32 구조체인 경우 0x10B, 64구조체인 경우 0x20B
- SizeOfCode : .text Section의 크기를 나타낸다.
- AddressOfEntryPoint : 프로그램 시작지점 코드(EP)의 주소가 RVA 값으로 저장되어 있다.
- BaseOfCode : 코드 영역이 시작되는 RVA
- ImageBase : PE파일이 메모리에 로드되는 시작주소. 보통 정해져있지만 설정에 따라 달라질 수 있다.
- SectionAlignment : 메모리에서 섹션의 최소단위. 메모리섹션의 시작주소는 반드시 이 값의 배수가 된다.
- FileAlignment : 파일에서 섹션의 최소단위.
- SizeOfImage : PE파일이 메모리에 로딩되었을때 전체 크기
- SizeOfHeaders : 모든 헤더의 크기를 나타낸다. (DOS Header + DOS Stub + PE Header + Section Header)
- SubSystem : 서브시스템을 구분한다. Driver(0x1), GUI(0x2), CLI(0x3) 등
- NumberOfRvaAndSizes : DataDirectory의 구조체 멤버의 개수를 나타낸다.
- DataDirectory : PE 파일에서 중요한 역할을 하는 개체들의 위치와 크기를 나타낸다.
- [0] Export Directory : EAT와 관련된 정보들을 담고있는 구조체를 가리킨다.
- [1] Import Directory : IAT와 관련된 정보를 가리킨다.
- [2] Resource Directory :
- [5] Base Relocation Directory : 재배치 관련된 데이터 구조에 대한
- [9] TLS Directory : TLS callback 함수를 이용한 안티리버싱 기법때문에 중요하다
- [B] Bound Import Directory :
4. IMAGE_SECTION_HEADER #
https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header
섹션 마다 하나씩 가지고있고, 파일과 메모리상에서의 섹션에 대한 정보를 담고있다.
1typedef struct _IMAGE_SECTION_HEADER {
2 BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
3 union {
4 DWORD PhysicalAddress;
5 DWORD VirtualSize;
6 } Misc;
7 DWORD VirtualAddress;
8 DWORD SizeOfRawData;
9 DWORD PointerToRawData;
10 DWORD PointerToRelocations;
11 DWORD PointerToLinenumbers;
12 WORD NumberOfRelocations;
13 WORD NumberOfLinenumbers;
14 DWORD Characteristics;
15} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
- VirtualSize : 메모리에서 섹션이 차지하는 크기
- VirtualAddress : 메모리에서 섹션의 시작주소 RVA. ImageBase와 더해서 VA를 찾을 수 있다.
- SizeoOfRawData : 파일에서 섹션이 차지하는 크기
- PointerToRawData : 파일에서 섹션의 시작 위치 (NULL 패딩 포함)
- Characteristics : 섹션의 특징
ex) .text(0x60000020), .data(0xC0000040), .rsrc(0x40000040)- CNT_CODE(0x20) : 섹션에 코드가 포함되는지
- CNT_INIT_DATA(0x40) : 섹션에 초기화 데이터가 포함되는지 .data영역
- CNT_UNINIT_DATA(0x80) : 섹션에 초기화되지 않은 데이터가 포함되는지 .bss영역
- MEM_EXECUTE(0x20000000) : 실행가능 권한 포함
- MEM_READ(0x40000000) : 읽기 권한 포함
- MEM_WRITE(0x80000000) : 쓰기 권한 포함
RAW to RVA #
PE 파일이 가상메모리에 올라갈때 그대로 올라가지 않고 ImageBase의 위치부터 올라가게 된다.
그리고 헤더의 위치는 그대로 올라가지만, 섹션들 사이의 간격이 더 벌어진다.
- RAW : PE파일이 로딩되기 전 파일에서의 오프셋 위치.
- RAW = RVA - SectionHeader.VA + PointerToRawData.
- 현재 위치(RVA)에서 해당 섹션의 시작주소(VA)를 빼면 섹션 내에서 위치 오프셋이 나온다. 파일에서 섹션의 시작주소를 더하면 원하는 RVA의 파일오프셋값이 나온다.
- ImageBase : 프로세스가 메모리에 올라갈때 시작주소를 의미한다. 32bit 시스템에선 exe: 0x00400000, dll:0x10000000 디폴트 였지만, 64bit 시스템에선 보안상의 이유로 프로그램 로더가 결정한다.
- VA : 프로세스 가상메모리의 절대주소. RVA + ImageBase = VA
- RVA : 프로세스 가상메모리의 상대주소
Section #
메모리에 올라갈때 ImageBase + 각 섹션 헤더의 VirtualAddress(RVA) 의 가상주소공간에 로드된다.
.text(r-x): 프로그램을 실행하기 위한 코드를 담고있는 섹션. 증분링크를 설정한경우 .textbss 가 생성된다.
.bss(rw-) : 초기화되지 않은 변수를 담은 섹션. 기본 0으로 초기화되는 영역. 메모리에 로드될때 .data섹션에 병합되기 때문에 PE에서 볼수있어도 메모리에선 볼 수 없다. 64bit 에선 PE파일에서도 병합됐다.
.data(rw-): 초기화된 변수를 담고있는 섹션. .bss 섹션도 있는데,
.rdata(r–): 읽기전용 데이터 섹션. 문자열 상수나 c++ 가상함수테이블 등이 있다. .edata, .debug 섹션도 이 섹션에 병합되고, .idata, didat 섹션도 증분링크 옵션을 해제하면 이 섹션으로 병합된다.
데이터디렉터리의 여러 엔트리도 이 섹션에 위치한다.
.reloc: 실행파일에 대한 기준 재배치 정보를 담고있는 섹션.
- 기준 재배치 : PE가 원하는 위치에 로드되지 못하고 재배치됐을경우 코드실행중 포인터 연산등의 주소참조를 할때 주소를 갱신해주는것을 말한다.
.edata: 내보낼 함수, 변수에 대한 정보를 담고있는 섹션. 보통 DLL의 PE파일에서 이 섹션을 발견할 수 있지만, 로드시 .rdata에 병합된다
.idata: 가져올 DLL, 함수, 변수에 대한 정보를 담고있는 섹션. IAT가 포함되는 섹션이고, 증분링크 옵션을 해제하면 .rdata에 병합된다.
.didat: 지연로딩을 위한 섹션이다.
.tls: Thread Local Storage의 데이터 초기화를 위한 섹션
.rsrc: 대화상자, 아이콘, 커서, 버전정보 등 리소스 관련 데이터들이 배치된다
.debug: 디버깅 관련 기초 정보가 담기는 섹션. 실제 정보는 PDB 파일에 별도로 보관한다.
.pdata: 예외정보를 담고있는 섹션이며 IMAGE_RUNTIME_FUNCTION_ENTRY 구조체의 배열로 구성된다. 테이블 베이스 예외처리를 사용하는 CPU 플랫폼에서만 제공되기 때문에 64bit PE에 존재하며 코드섹션 함수 분석에도 중요한 역할을 한다.