[Reversing] 리버싱 핵심원리 [12]. PE 헤더 1

2020. 4. 2. 21:25보안/리버싱

728x90

내용 : Windows 운영체제의 PE File Format에 대해 공부한다. Windows 운영체제의 가장 핵심적인 부분인 Process, Memory, DLL 등에 대한 내용을 정리한다.

 

PE File Format

PE file 종류  :https://ddungkill.tistory.com/24

 

 

 

기본 구조

PE 헤더 -> DOS 헤더, DOS Stup, NT 헤더, Section 헤더

 

DOS 헤더

제작 당시 DOS 파일에 대한 하위 호환성을 고려하여 제작함 / 가장 먼저 나오는 부분.

IMAGE_DOS_HEADER 구조체가 존재함

typedef struct _IMAGE_DOS_HEADER
{
     WORD e_magic;			//DOS signature {4D5A}
     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; 			// NT header의 옵셋을 표시 (파일에 따라 가변적임)
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

 

 

 

 

DOS stup

파일에 따라 존재여부가 달라짐, Windows Os에서는 실행되지 않음.(PE 파일이라 인식하기 떄문임.)

DOS 모드에서 실행가능

 

 

 

 

NT Header

FileHeader / OptionalHeader을 가짐

typedef struct _IMAGE_NT_HEADERS {
	DWORD Signature; //4
	IMAGE_FILE_HEADER FIleHeader;
	IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} Image_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

 

 

IMAGE_FILE_HEADER

typedef struct _IMAGE_FILE_HEADER {
	WORD Machine; // CPU 별 고유 값을 가짐 | (X86 : 0X14C, X64 : 0X8664)
	WORD NumberOfSections; //섹션의 개수 0보다 큰 값을 가짐, 정의된 값과 실제 값이 다르면 에러
	DWORD TimeDateStamp; // 빌드 시간
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader; //IMAGE_OPTIONAL_HEADER32의 크기를 나타낸다
    // PE32+ 형태 파일의 경우 IMAGE_OPTIONAL_HEADER64 구조체 사용 -> 두 구조체의 크기가 다르기 때문에
    // 구조체 크기를 명시함
    
	WORD Characteristics; // 파일의 속성을 나타냄, 실행가능한 형태인지 bit OR 방식으로 조합
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 

 

 

IMAGE_OPTIONAL_HEADER

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic; //IMAGE_OPTIONAL_HEADER32 구조체 : 10B, "64 구조체 : 20B값을 가짐
  BYTE                 MajorLinkerVersion; 
  BYTE                 MinorLinkerVersion; 
  DWORD                SizeOfCode; 
  DWORD                SizeOfInitializedData; 
  DWORD                SizeOfUninitializedData; 
  DWORD                AddressOfEntryPoint; //EP의 RVA값을 가지고 있음, 프로그램 실행시 최초로 실행되는 코드의 시작 주소
  DWORD                BaseOfCode; 
  DWORD                BaseOfData; 
  
  DWORD                ImageBase;
  
 // 프로세스의 가상 메모리 주소(X86) : 0 ~ FFFFFFFF
 // EXE | DLL 로딩 주소 :  0 ~ 7FFFFFFF -> USER MEMORY SECTOR
 // SYS 로딩주소 : 80000000 ~ FFFFFFFF -> KERNEL SECTOR
 // 개발도구가 만들어내는 EXE의 IMAGE BASE : 00400000
 // DLL 파일의 IMAGEBASE : 10000000
 // PE 로더는 PE 파일 실행을 위해 EIP값을 IMAGEBASE + AddressOfEntryPoint 값으로 세팅
 
  DWORD                SectionAlignment; //메모리에서 섹션의 최소단위를 나타내는것
  DWORD                FileAlignment; // 파일에서 섹션의 최소 단위를 나타내는것
  // * 파일에서 SectionAlignment와 FileAlignment의 값은 같거나 다를수 있음
  
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  
  DWORD                SizeOfImage; //PE 파일이 로딩되었을 때, 가상메모리에서 PE Image가 차지하는 크기
  DWORD                SizeOfHeaders; //PE 헤더의 전체 크기
  
  DWORD                CheckSum; //64
  WORD                 Subsystem; 
  //시스템 드라이버(.sys)인지 일반 실행파일(.exe | .dll)인지 구분할 수 있음
  // 1. Driver file - 시스템 드라이버
  // 2. GUI file - 창 기반 에플리케이션
  // 3. CUI file - 콘솔 기반 에플리케이션
  
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit; 
  DWORD                SizeOfHeapReserve; 
  DWORD                SizeOfHeapCommit; 
  DWORD                LoaderFlags; 
  
  DWORD                NumberOfRvaAndSizes; 
  //IMAGE_OOPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타냄.
  
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; 
  //구조체의 배열로, 각 배열 항목을 나열함
  //아래에 구조체 배열 첨부
  
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; 


파일 실행에 필수적이어서, 잘못 세팅되면 파일이 정상실행이 되지 않는다.

 

/* DATA DICTIONARY */
DataDirectory[0] = EXPORT Directory // 주목하기
DataDirectory[1] = IMPORT Directory // 주목하기
DataDirectory[2] = RESOURCE Directory //주목하기
DataDirectory[3] = EXCEPTION Directory
DataDirectory[4] = SECURITY Directory
DataDirectory[5] = BASERELOC Directory
DataDirectory[6] = DEBUG Directory
DataDirectory[7] = COPYRIGHT Directory
DataDirectory[8] = GLOBALPTR Directory
DataDirectory[9] = TLS Directory // 주목하기
DataDirectory[A] = LOAD_CONFIG Directory
DataDirectory[B] = BOUND_IMPORT Directory
DataDirectory[C] = IAT Directory
DataDirectory[D] = DELAY_IMPORT Directory
DataDirectory[E] = COM_RESCRIPTOR Directory
DataDirectory[F] = Reserved Directory

Export, Import, resource, TLS Dictionary 눈여겨보기

특히, IMPORT, EXPORT Dictionary는 중요한 역할을 함

 

 

 

 

 

 

PE 바디 -> Section (text, data, rsrc)

 

IMAGE_OPTIONAL_HEADER

 

#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;	// 메모리에서 섹션의 시작주소 (RVA)
  DWORD SizeOfRawData; // 파일에서 섹션이 차지하는 크기
  DWORD PointerToRawData; //파일에서 섹션의 시작 위치
  DWORD PointerToRelocations;
  DWORD PointerToLinenumbers;
  WORD  NumberOfRelocations;
  WORD  NumberOfLinenumbers;
  DWORD Characteristics; // 섹션의 속성 (bit OR)
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

VirtualAddress와 PointerToRawData는 각각 IMAGE_OPTIONAL_HEADER32에 정의된 SectionAlignment와 FileAlignment에 맞게 결정된다.

 

VirtualSize와 SizeOfRawData는 일반적으로 서로 다른 값을 가진다.

(파일에서 섹션 크기와, 메모리에 로딩된 섹션의 크기는 다름.)

 

#define IMAGE_SCN_CNT_CODE	0x00000020 // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA	0X00000040 // Section contains initialized data.
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080	// Section contains uninitialized data.
#define IMAGE_SCN_MEM_EXECUTE	0X20000000 // Section is executable.
#define IMAGE_SCN_MEM_READ	0x40000000 //Section is readable.
#define IMAGE_SCN_MEM_WRITE 0x80000000 // Section is writable.

위 코드는 Characteristics 파일의 형식이다.

 

#PE파일이 메모리에 로딩될 때 파일이 그대로 올라가는것이 아니라, 섹션 헤더에 정의된 대로 시작주소, 섹션 크기 등에 맞춰서 올라가게 된다. 따라서 파일에서의 PE와 메모리에서의 PE는 서로 다른 모양을 가진다.

이를 구별하기 위해서 메모리에 로딩된 상태를 이미지라는 용어를 사용하여 구별한다.

 

RVA to RAW

PE파일이 메모리에 로딩되었을 때 각 섹션에서 메모리의 주소(RVA)와 파일 옵셋을 매핑할 수 있어야 한다.

이러한 매핑 방법을 RVA to RAW라고 한다.

 

=======================================

1. RVA가 속해있는 섹션을 찾는다.

2. 간단한 비례식을 사용하여 파일 옵셋(RAW)을 계산한다.

=======================================

 

IMAGE_SECTION_HEADER에 따르면 비례식은

RAW - PointerToRawData = RVA - VirtualAddress

RAW = RVA - VirtualAddress + PointerToRawData

 

#해당 내용은 리버싱 작업에서 가장 기본이 되는 내용이므로 꼭 숙지하기!

 

 

728x90