본문 바로가기
Reverse Engineering/리버싱 핵심 원리

2부 PE File Format(0)

by Ken out of ken 2025. 1. 2.

PE32-format

출처

 

File:Portable Executable 32 bit Structure in SVG fixed.svg - Wikimedia Commons

 

commons.wikimedia.org

 

 

 

PE FILE FORMAT

PE파일은 섹션 헤더에 각 섹션에 대한 파일/메모리에서의 크기, 위치, 속성 등이 정의 되어있다

헤더의 끝과 각 섹션의 끝에는 NULL padding이라고 불리우는 영역이 존재하는데, 이는 컴퓨터에서 파일, 메모리, 네트워크 패킷 등을 효율적으로 처리하기 위한 기본 단위 개념을 사용하기위해 각 섹션의 시작 위치를 각 파일/메모리의 최소 기본 단위의 배수에 해당하는 위치로 옮기고 빈 공간을 NULL로 채우기 때문이다   

 

PE 파일이 메모리에 로딩되는 모습

 

 

VA & RVA

 

  • VA: 프로세스 가상 메모리의 절대 주소
  • RVA (Relative Virtual Address):  ImageBase에서 부터의 상대 주소 

$$ VA = RVA + ImageBase $$

 

왜 RVA가 필요하고 대부분 PE 헤더 내의 정보가 RVA 형태를 띌까?

그 이유는 메모리를 동적으로 할당하는 부분에 있어, 언제든 다른 파일들이 메모리에 어디든 로드될 수 있기에 절대 경로는 안전하지 않다

그리고 DLL의 특성상 언제든 로드되고 언로드 될 수 있기 때문에 각 PE 파일마다 상대경로를 만들어 두는 것이 훨씬 안전하다 

위와 같이 가상 메모리의 특정 위치에 로딩될 때 이미 그 위치에 다른 파일이 로딩되어 있는 경우 다른 위치로 로딩하는 기법을 재배치(Relocation) 과정 이라고 한다

 

 

PE FILE HEADERS

DOS Header

PE File Format을 만들 때 당시에 사용하던 DOS 파일에 대한 하위 호환성을 고려해서 DOS EXE Header를 확장시킨 IMAGE_DOS_HEADER 구조체가 존재한다

 

typedef struct _IMAGE_DOS_HEADER
{
     WORD e_magic;				// 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 to NT header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

 

lMAGE_DOS_HEADER 구조체의 크기는 40바이트 이다

 

여기서 가장 중요한 멤버는 주석 처리한 e_magic, e_lfanew로 모든 PE 파일의 시작에는 DOS signature ("MZ")가 존재하고 e_lfanew 값이 가리키는 위치에 NT Header 구조체가 존재해야 한다

 

NT Header

typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;		// PE Signature : 50450000 ("PE"00)
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

 

 

아래 두개 FileHeader,  OptionalHeader에 대해서 알아보자!

 

IMAGE_FILE_HEADER

typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;			// CPU별로 고유한 값 (intel x86호환 칩은 14C의 값)
  WORD  NumberOfSections;		// 섹션의 개수 (0보다 커야하며 실제 개수와 다르면 에러)
  DWORD TimeDateStamp;			// 파일의 빌드 시간 (실행에 영향 x)
  DWORD PointerToSymbolTable;				
  DWORD NumberOfSymbols;					
  WORD  SizeOfOptionalHeader;		// IMAGE_OPTIONAL_HEADER[32, 64] 구조체의 크기
  WORD  Characteristics;		// 파일의 속성을 나타내는 값 (0002: 실행, 2000: DLL)
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 

notepad.exe

 

IMAGE_FILE_HEADER 구조체 멤버로 표현하면 다음과 같다

 

멤버 이름 오프셋 설명
Machine 0x104 0x8664 x64 Architecture
NumberOfSections 0x106 0x0007 섹션 7개
TimeDateStamp 0x108 0x61FC30C0 2022-01-03 06:48:00 UTC
PointerToSymbolTable 0x10C 0x00000000 심볼 테이블 없음
NumberOfSymbols 0x110 0x000000F0 심볼 240개
SizeOfOptionalHeader 0x114 0x0022 Optional Header: 34바이트
Characteristics 0x116 0x020B 실행 파일 (EXE)

 

 

OPTIONAL HEADER

PE 헤더 구조체 중에서 가장 크기가 크다

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;

 

굉장히 많은 내용들이 보인다

여기서 주목해야할 멤버들은 굉장히 많다

 

  • MAGIC
    32의 경우에는 10B, 64의 경우에는 20B의 값을 가진다
  • AddressOfEntryPoint
    EP의 RVA 값을 가지고 있다
    프로그램에서 최초로 실행되는 코드의 시작 주소로, 매우 중요하다
  • ImageBase
    프로세스의 가상 메모리는 0~FFFFFFFF(32) 범위로 여기서 PE 파일이 로딩되는 시작 주소를 나타낸다
    EXE, DLL 파일은 user memory 영역인 0~7FFFFFFF 범윙 로딩되고,
    SYS 파일은  kernel memory 영역인 80000000~FFFFFFFF 범위에 로딩된다
    PE 로더는 PE 파일을 실행시키기 위해 프로세스를 생성하고 파일을 메모리에 로딩한 후 EIP 레지스터값을 ImageBAse + AddressOfEntryPoint 값으로 세팅한다
  • SectionAlignment, FileAlignment
    메모리에서 섹션의 최소단위, 파일에서 섹션 최소 단위를 나타낸다
    위에서 말했듯 윈도우프로그램들은 해당 단위의 배수에 파일/프로그램을 위치시킴으로서 최적화를한다
  • SizeOfImage
    PE 파일이 메모리에 로딩되었을 때 갓아 메모리에서 PE Image가 차지하는 크기
    일반적으로 파일의 크기와 메모리에 로딩된 크기는 다르다
  • SizeOfHeader
    PE 헤더의 전체 크기
    FileAlignment의 배수여야 한다
    파일에서 해당 offset만큼 떨어진 위치에 첫 번째 섹션이 위치한다
  • Subsystem
    시스템 드라이버 파일(*.sys)인지, 일반 실행 파일(*.exe, *.dll)인지 구분할 수 있다
  • NumberOfRvaAndSizes
    마지막 멤버인 DataDirectory 배열의 개수
    구조체 정의에 #define IMAGE_NUMBEROF_DIRECTORY_ENRIES 16 으로 정의되어 있으나, PE로더는 이 멤버를 보고 배열의 크기를 인식한다
  • DataDirectory
    IMAGE_DATA_DIRECTORY 구조체의 배열
    배열의 각 항목 마다 정의된 값을 가진다

 

이제 notepad.exe의 IMAGE OPTIONAL HEADER를 살펴보자

 

멤버 이름 데이터 설명
Magic 0B 02 0x020B PE32+ (x64)
AddressOfEntryPoint 00 C0 3B 02 0x023BC000 실행 시작 지점 (RVA)
ImageBase 40 01 00 00 00 00 10 00 0x0000000100004000 이미지의 기본 로드 주소
SectionAlignment 00 02 00 00 0x00000200 섹션 정렬 크기
FileAlignment 0A 00 00 00 0x0000000A 파일 정렬 크기
SizeOfImage 60 C1 00 00 0x0000C160 전체 이미지 크기 (섹션 정렬 기준)
SizeOfHeader 08 00 00 00 0x00000008 헤더 크기
Subsystem 00 10 0x0010 실행 환경
NumberOfRvaAndSizes 10 00 00 00 16 데이터 디렉터리 개수