본문 바로가기

공부/리버싱 핵심원리

PE File Format


 

1. 소개

Windows 운영체제는 기본적으로 파일의 포맷을 PE File Format으로 사용
 
분류

32비트 형태의 실행 파일: PE 또는 PE32
64비트 형태의 실행 파일: PE+ 또는 PE32+
 


 

2. PE File Format

종류주요 확장자
실행 계열EXE, SCR
라이브러리 계열DLL, OCX, CPL, DRV
드라이버 계열SYS, VXD
오브젝트 파일 계열OBJ

32비트 형태의 실행 파일: PE 또는 PE32
64비트 형태의 실행 파일: PE+ 또는 PE32+


2.1 기본 구조

구성: "PE Header" + "PE Body"
          헤더: 이 파일에 대한 대략적인 정보들을 갖고 있음
          바디: 실제 데이터들은 바디에 들어가 있음
섹션 헤더: 각 섹션에 대한 파일/메모리의 크기 및 위치, 속성 등이 정의되어 있다.
NULL padding:
          일반적으로 효율성을 위해 파일/메모리의 크기는 최소 기본 단위 사용
          PE 파일도 이 원칙을 적용
          -> 헤더와 각 섹션은 최소 기본 단위의 배수에 해당되는 위치부터 시작
          -> 실제 사용하지 않는 나머지 부분은 NULL로 패딩

notepad.exe 파일의 시작 부분  (PE 파일의 header 부분)

2.2 VA & RVA

VA (Vitual Address): 프로세스 가상 메모리의 절대 주소
RVA (Relative VA): 기준 위치(ImageBase)로부터의 상대 주소
          PE 헤더 내의 정보는 RVA 형태가 많음
ImageBase + RVA = VA
 


 

3. PE 헤더

3.1 PE 헤더 - DOS Header

DOS 파일에 대한 호환성을 고려하여 만든 헤더
헤더의 앞 부분에는 기존 DOS EXE Header를 확장시킨 64바이트 크기의 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_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

주요 field
          1) e_magic: DOS의 signature를 나타냄. MZ값이 와야 함.
          2) e_lfanew: NT Header의 offset을 나타냄. (파일에 따라 값이 가변적)

e_magic 부분: MZ의 아스키 값인 4D5A가 나타남
e_lfanew 값: 000000E0 (little endian임에 주의)가 나타남


3.2 PE 헤더 - DOS Stub

DOS Header 다음에 위치한다.

DOS Stub 부분


존재 여부는 Optional이며, 크기도 가변적이다. 
-> DOS Header의 e_lfanew 값을 이용

코드데이터가 함께 구성 됨
Windows 32비트 운영체제에서는 이 부분의 코드가 실행되지 않는다 -> PE 파일로 인식하여 이 부분을 무시한다.
DOS 환경이나 DOS용 디버거를 이용하여 실행하면 이 코드 부분이 실행된다.
-> windows XP 환경에서 컴맨드 창에 다음과 갈이 입력. debug c:₩windows₩notepad.exe
->  커서에 'u'명령 입력 (unassemble)
-> 상단의 그림과 같은 화면이 출력된다.
-> 문자열 출력 후, 종료


3.3 PE 헤더 - NT Header

NT Header 구조체: IMAGE_NT_HEADER

typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;


1) 크기: 248 바이트 (F8h)
2) Field
          1) Signature: 50450000h ("PE"00) 값
          2) FileHeader
          3) OptionaHeader

Signature 부분

3.4 PE 헤더 - NT Header  - File Header

파일의 개략적인 "속성"을 표시 (IMAGE FILE HEADER) : 20 바이트 (WORD-2바이트, DWORD-4바이트)

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;

주요 필드
1 Machine: CPU 별 고유 값 -> 32비트 Intel x86 호환칩: 0x014C
2 NumberofSections: PE파일에 저장된 세션의 수
3 SizeofOptonalHeader: IMAGE NT_HEADERS32 구조체의 마지막 field인 IMAGE OPTIONAL HEADER32 의 크기
      -> PE2+ 형태의 파일은 IMAGE_OPTIONALHEADER62를 가지기 때문에 구조체 크기의 차이가 발생한다.
          이를 해결하기 위하여 구조체의 크기를 명시한다.
4 Characteristics: 파일의 속성을 표시
      -> 실행 파일인지 혹은 DLL 파일인지등의 정보를 bit OR 형식으로 제공
      -> File is executable : 0x0002
      -> link 참조: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_nt_headers32
5 symbol = 정적함수, 변수 -> Symbol Table은 이러한 함수/변수의 주소값을 정리한 테이블

IMAGE_FILE_HEADER 구조체 부분

3.5 PE 헤더 - NT Header  - Optional Header

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

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;

1) Magic:
          IMAGE_OPTINAL_HEADER32: 010B
          IMAGE_OPTINAL_HEADER64: 020B

2) AddressOfEntryPoint
          EP의 RVA 값
          프로그램에서 최초로 실행되는 코드의 시작 주소 값

3) ImageBase
          32비트의 경우, 프로그램의 가상메모리는 주소 범위 :0~FFFFFFFF
          가상메모리에서 PE 파일이 로딩되는 시작 주소
                     EXE, DLL 파일: 0~7FFFFFFF (User Memory 영역)
                     SYS 파일: 80000000~FFFFFFFF (Kernel Memory 영역)
          PE로더는 PE파일을 실행하기 위해 프로세스를 생성하고,
          파일을 로딩한 후 EIP = ImageBase + AddressofEntrypoint 로 VA 설정한다.
 
4) SectionAlignement & FileAlignment
          SectinAlignment: "메모리"에 저장된 섹션의 최소 단위
          FileAlignment:     "파일"에서 섹션의 최소 단위
          파일/메모리에 저장된 PE 파일의 Body를 구성하는 섹션의 크기는 각각 이 두 값의 배수가 되어야 한다.

5) SizeOflmage
          PE 파일이 메모리에 로딩될 때 가상 메모리에서 PE lmage의 크기

6) SizeOfHeaders
          PE 헤더의 전체 크기: 파일 시작에서 SizeOfHeaders 옵셋만큼 떨어진 위치에 슈 섹션이 존재
          FileAlignment의 배수

7) Subsystem: *.sys 파일인지 또는 일반 실행 파일(*.exe, *.dl)인지를 구분한다
          1: driver file -> 시스템 드라이버. ex: *.ntfs, *.sys
          2: GUI file -> 창 기반 Application. ex: notepad.exe
          3: CUI file -> 콘솔 기반 Applicatin. ex: cmd.exe

8) NumberofRvaAndsizes: DataDirectory 배열의 개수
    -> 배열이 2개 이상 있을 수 있나? ( 지금까지 1만 보고 2 이상 쓰고 있는 파일이 어떤 파일인지 아직 보지를 못함)

9) DataDirectory: IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목마다 정의된 값을 지닌다. 각 항목은 다음과 같다.

DataDirectory[0] = EXPORT Directory //중요
DataDirecotry[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 Direectory
DataDirectory[E] = COM_DESCRIPTOR Directory
DataDirectory[F] = Reserved Directory
IMAGE_OPTIONAL_HEADER 구조체 부분

3.6 섹션 헤더 (Section Header)

각 섹션의 속성(Property)을 정의
 
각 섹션별로 IMAGE_SECTION_HEADER 구조체의 배열로 되어 있다.

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;


섹션으로 구분된 데이터 (code, data, resource, etc) 관리
 : 프로그램의 안전성 확보 -> ex. overflow 발생으로 인한 오류 예방

 
메모리 속성별 접근 권한

종류 접근 권한
code실행, 읽기 권한
data비실행, 읽기, 쓰기 권한
resource비실행, 읽기 권한

 
섹션 헤더의 개수 = NT_Header. FileHeader. NumberOfSections 에 기록

주요 field
         1 VirtualSize:             메모리에서 섹션이 차지하는 크기
         2 VirtualAddress:       메모리에서 섹션의 시작주소(RVA)
         3 SizeOfRawData:     파일에서 섹션이 차지하는 크기
         4 PointerToRawData: 파일에서 섹션의 시작 위치
         5 Characteristics:       섹션의 속성 (bit OR로 표시)

VirtualAddress와 PointerToRawData는
sectionAlignment와 FileAlignment의 조건에 맞게 결정된다.

Name: 별다른 규정이 없음. NULL로 끝나지 않음 (8바이트)

Characteristics

Define Value 설명
IMAGE_SCN_CNT_CODE0x00000020섹션에 코드가 포함됨
IMAGE_SCN_CNT_INITIALIZED_DATA0x00000040섹션에 초기화된 데이터가 포
IMAGE_SCN_CNT_UNINITIALIZED_DATA0x00000080섹션에 초기화되지 않은 데이터가 포함
IMAGE_SCN_MEM_EXECUTE0x20000000섹션 실행 가능
IMAGE_SCN_MEM_READ0x40000000섹션 읽기 가능
IMAGE_SCN_MEM_WRITE0x80000000섹션 쓰기 가능
IMAGE_SECTION_HEADER 부분

메모장의 경우 3개의 섹션이 존재하므로 3 부분이 있다. Name 멤버를 기준으로 3개(.text / .data / .rsrc)


 

4. RVA to RAW

Image: PE 파일의 정보가 메모리에 로딩된 상태를 지칭

섹션에서 메모리 시작 주소 값인 VirtualAddress와
파일의 시작 주소/옵셋 값인 PointerToRawData은 다를 수 있다.

그러므로 메모리와 파일 사이에 데이터 매핑이 필요하다 -> RVA to RAW ; RAW to RVA

 
메모리 주소 값(RVA)을 이용하여 File에서 대응되는 RAW 값 찾기

(방법)
RAW - PointerToRawData = RVA - VirtualAddress
                                 RAW = RVA - Virtualaddress + PointerToRawData
1) RVA 값이 속한 섹션을 찾는다
2) 해당 섹션의 VirtualAddress와 PointerToRawData 값을 섹션 헤더에서 읽는다
3) 수식을 이용하여 RAW (=File Offset)를 계산한다.

 


 

5. IAT (lmport Address Table)

process, memory, dll 구조등에 대한 내용이 기록됨

 
프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블


5.1 DLL

DLL (Dynamic Linked Library)

 
멀티 태스킹을 수행할 때 정적라이브러리의 비효율성을 개선하기 위해 개발됨
    1) 개별 프로그램에 라이브러리를 각각 포함시키지 말고 별도로 DLL로 구성하여 필요할 때마다 불러 쓰자
    2) 한번 로딩된 DLL의 코드와 리소스는 Memory Mapping 기술로 여러 프로세서에서 공유하여 쓰자
    3) 라이브러리의 업데이트가 필요하면, 해당 DLL 파일만 교체하자.

종류
1) 명시적 링크 (Explicit Linking): 프로그램에서 사용되는 순간에 로딩되고, 사용이 끝나면 메모리에서 해제 된
2) 암시적 링크 (lmplicit Linking); 프로그램 시작할 때 같이 로딩되고, 프로그램이 종료될 때 메모리에서 해제 됨

 
IAT는 Implicit Linking에 대한 메커니즘을 제공한다.


5.2 IDT - IMAGE_IMPORT_DESCRIPTOR

PE 파일은 프로그램이 import 하여 사용할 라이브러리를
IDT에 IMAGE_IMPORT_DESCRIPTOR (IID)구조체로 명시한다.
-> 프로그램 로딩 시 IAT 갱신을 위해 사용


프로그램은 여러 개의 라이브러리를 사용하기 때문에,
라이브러리 개수만큼 구조체 배열 형식으로 존재
-> 구조체 배열의 마지막 원소는 NULL구조체

IMAGE_OPTIONAL_HEADER32.DataDirector[1].VirtualAddress 값이 IID 구조체의 시작 주소 (RVA) 값이다

 

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
	union {
    	DWORD Characteristics;
        DWORD OriginalFirstThunk;	//INT(Import Name Table) address (RVA)
	};
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name;	//library name string address (RVA)
    DWORD FirstThunk;	//IAT(Import Address Table) address (RVA)
    
} IMAGE_IMPORT_DESCRIPTOR;

typedef struct _IMAGE_IMPORT_BY_NAME {
	WORD Hint;	//ordinal
    BYTE Name[1];	//function name string
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;


IID 구조체 주요 field
     1) OriginalFirstThunk: INT(Import Name Table)의 주소 (RVA) -> 함수정보
     2) Name: Library 이름 문자열 주소 (RVA)
     3) FirstThunk: IAT (Import Address Table)의 주소 (RVA)

 

임포트 함수 주소를 IAT에 기록하는 순서
     1) IID의 Name 필드에서 라이브러리 이름을 읽는다: kernel32.dll
     2) 해당 라이브러리를 로딩한다.
          -> LoadLibrary("kernel32.dll") 함수 호출
     3) IID의 OriginalFirstThunk 필드에서 INT 주소를 읽는다.
     4) INT에서 배열의 값을 하나씩 확인하여 해당 IMAGE_IMPORT_BY_NAME의 주소(RVA)를 읽는다.
     5) IMAGE_IMPORT_BY_NAME의 Hint 또는 Name 필드를 이용하여, 해당 함수의 시작 주소값을 얻는다
         -> GetProcAddress("GetCurrentThreadld") 함수 호출
     6) IID의 FirstThunk필드에서 IAT 주소를 읽는다.
     7) IAT 배열에 step-5에서 확보한 주소를 입력한다
     8) INT가 끝날 때까지 4~7을 반복한다. (NULL 배열)


 



'공부 > 리버싱 핵심원리' 카테고리의 다른 글

UPX 언패킹  (0) 2024.05.12
IAT 따라가기  (0) 2024.05.12
Lena's Reversing for Newbies  (0) 2024.04.17
abex' crackme #2 분석  (0) 2024.04.17
abex' crackme #1 분석  (0) 2024.04.15