Abstract
PE 포맷의 OptionalHeader에는 프로그램 실행의 시작 지점인 Entry Point를 비롯해 다양한 부가 정보를 가진다.
Optional Header
IMAGE_OPTIONAL_HEADER structure
Optional 헤더는 PE 파일이 메모리에 올라갈 위치인 Image Base와 IAT 등의 위치를 기록한 Data Direcotry 등이 있다. 32비트 프로그램에서 winnt.h에 정의된 Optional 헤더는 아래와 같다.
//
// Optional header format.
//
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
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;
64비트 프로그램의 Optional 헤더는 아래와 같다.
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG 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;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
필드가 상당히 많은만큼 주요 필드도 많다.
Magic
IMAGE_OPTIONAL_HEADER32의 경우 10Bh의 값을 가지고 IMAGE_OPTIONAL_HEADER64의 경우 20Bh의 값을 가진다.
AddressOfEntryPoint
프로그램 코드가 시작되는 시작 주소를 RVA 값으로 지니고 있다. ImageBase 값에서 AddressOfEntryPoint 값을 더하면 그 주소가 메모리 상에서의 코드 시작 주소 VA가 된다. ASLR이 적용된 경우엔 새 ImageBase 값이 더해진 EntryPoint가 코드 시작 주소 VA가 된다.
ImageBase
메모리에 PE 파일 데이터를 올릴 기준 주소다. 만약 ImageBase 값이 400000h라면 PE 파일이 메모리에 로드될 때 400000h부터 파일 데이터를 올린다. 아래는 ImageBase 값이 400000h이며 ASLR이 적용되지 않은 실행 파일의 메모리를 동적 디버거로 확인한 모습이다. ImageBase 값인 400000h로 가면 DOS 헤더와 PE 헤더가 나타나는 것을 볼 수 있다.
SectionAlignment
코드나 문자열, 전역변수 등이 저장되는 모든 공간은 섹션을 단위로 나누어진다. 일반적으로 코드 섹션은 .text 섹션에 저장되고, 문자열 등은 .data 섹션에 저장된다. SectionAlignment는 PE 파일과 섹션이 메모리에 로딩될 때 각 섹션이 가지는 메모리 공간의 최소 단위 크기를 말한다. 예를 들어 SectionAlignment 값이 1000h라면 각 섹션은 1000h의 배수로 크기를 가진다. 보통 메모리 페이지 크기인 1000h(4096) 바이트 크기를 가진다. 섹션 헤더의 Virtual Address 값들을 SectionAlignment의 배수로 설정되는 것을 볼 수 있다. 아래 그림은 .text 섹션의 Virtual Address이자 SectionAlignment 값의 배수인 401000h에 .text 섹션 데이터가 로딩된 모습이다.
FileAlignment
FileAlignment는 파일 에서의 최소 단위 크기를 말한다. 보통 하드디스크의 섹터 크기인 200h로 설정된다. 메모리에 로딩될 땐 SectionAlignment의 배수로 로딩되는 것 처럼 파일로 저장될 때는 FileAlignment의 배수에 각 섹션들이 위치한다. 아래 그림은 FileAligment의 배수이자 .text 섹션의 PointerToRawData 값인 400h부터 어셈블리 코드가 나타나는 모습이다.
SizeOfImage
PE 파일이 메모리에 로딩됐을 때 차지하는 메모리 전체 크기를 말한다. SectionAlignment의 배수 단위로 각 섹션을 배치하기 때문에 파일 크기와 메모리에 올라갔을 때 크기가 다르다.
SizeOfHeader
PE 헤더의 전체 크기를 말한다. 파일 상에서의 크기를 말하는 것으로 FileAlignment의 배수여야 한다. SizeOfHeader 값에 해당하는 오프셋으로 가면 첫번째 섹션의 데이터가 나타난다.
Subsystem
PE 파일이 드라이버 파일(.sys)인지 .exe나 .dll 파일이 구분할 수 있고, CLI인지 GUI 프로그램인지도 구분할 수 있다. 콘솔에서 동작하는 프로그램은 3 값을 가진다. 더 많은 Subsystem 값은 MSDN을 참고하면 된다.
NumberOfRvaAndSizes
IAT 등의 정보가 어디 담겼는지 알려주는 DataDirectory 구조체 배열의 원소 개수를 나타낸다. winnt.h 상에서 IMAGE_OPTIONAL_HEADER의 DataDirectory 구조체 배열은 16개의 원소 개수를 고정적으로 가지도록 하고 있지만 PE 로더는 NumberOfRvaAndSizes 값을 보고 DataDirectory 구조체 배열을 인식한다. 때문에 DataDirectory가 16개가 아닐 수 있다.
DataDirectory
DataDirectory는 IMAGE_DATA_DIRECTORY 구조체의 배열이다. IMAGE_DATA_DIRECTORY 구조체는 4바이트의 VirtualAddress 멤버와 4바이트의 Size 멤버를 가질뿐 구조체 자체가 어떤 데이터를 의미하는지는 나타내지 않는다.
//
// Directory format.
//
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
때문에 DataDirectory의 인덱스로 어떤 데이터를 의미하는지 나타낸다. 각 인덱스별 의미는 아래와 같다. 이 역시 winnt.h에 정의되어 있다.
// Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
주요 항목은 아래와 같다.
- DataDirectory[0]: Export Directory, DLL이 외부로 export 하는 함수 이름과 관련한 데이터의 위치를 나타낸다.
- DataDirectory[1]: Import Directory, PE 파일이 외부 DLL에서 import 할 DLL 이름과 함수 이름이 나열된 데이터의 위치를 나타낸다.
- DataDirectory[12]: IAT Directory, PE 파일이 사용하는 IAT의 위치를 나타낸다. Import Directory를 읽어 가져온 함수의 주소가 이 위치에 덮어쓰기 된다.
'Malware Analysis > PE' 카테고리의 다른 글
#6 PE - .reloc 섹션 (0) | 2021.08.01 |
---|---|
#5 PE - IAT (0) | 2021.08.01 |
#4 PE - Section Header (0) | 2021.08.01 |
#2 PE - File Header (0) | 2021.08.01 |
#1 PE - DOS Header (0) | 2021.07.31 |