본문 바로가기
Study/Reversing

[Reversing] PE Header 개념 정리 & wow64 fs redirection

by Jamie Lim 2020. 9. 21.

PE 파일

PEPortable Executable은 이식 가능한 실행 파일이라는 뜻으로 하나의 실행 파일을 여러 운영체제에서 실행 가능하다는 의미다. 유닉스 실행 파일 형식인 COFF(Common Object File Format)을 기반으로 exe, dll, obj, sys 등의 확장자를 가진 파일들이 이에 해당한다고 볼 수 있다.

 

PE 파일 종류

1. 실행 계열 : exe, scr

 

2. 드라이버 계열 : sys, vxd

 

3. 라이브러리 계열 : dll, ocx, cpl, drv

 

4. 오브젝트 계열 : obj

 

VA RVA

VA

  > 프로세스 가상 메모리의 절대주소

RVA

  > ImageBase로부터의 상대주소 (메모리에 로딩된 상태)

 

  > PE 메모리에 적재되기 전 기본 ImageBase 0이다. 그리고 PE안에서 상대주소로 주소가 적히게 된다. 이는 메모리 적재 시 절대주소에 비해 상대주소가 ImageBase에서 얼만큼 떨어진 곳으로 이동하기 때문에 재배치가 더 쉽게 이루어진다.

 

* RVA + ImageBase = VA

 


PE 파일 기본 구조

* 위 사진에서 address는 메모리에서의 절대 주소, 즉 위치를 표현한 것으로 VA라고도 한다

 

PE Header

1. DOS Header (64 byte, 0x40 고정)

PE 헤더의 제일 앞 부분에 기존 DOS EXE Header를 확장해 만든 것이 MAGE_DOS_HEADER 구조체이다.

아래 구조체들 중 우리가 볼 부분은 e_magic e_lfanew.

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;


WORD e_magic : DOS signature로 항상 45 5A(ASCII 값으로 “MZ”) 

WORD e_lfanew : NT Heaer offset으로 파일에 따라 가변적인 값을 갖는다

                 이 값이 가리키고 있는 위치에 NT Header 구조체가 있어야 한다

 

 

 

2. DOS Stub

DSO Header 밑에 있으며 옵션에 의해 존재 여부를 경정하기 때문에 크기가 일정하지 않다. DOS Header와는 다르게 없어도 파일 실행에는 문제가 없다.

DOS Header에서 e_lfanew가 가변적인 이유가 바로 이 DOS Stub이 가변적이기 때문이다. 그렇기에 DOS Stub 밑에 있는 DOS Header e_lfanew의 필드 값이 변하는 것이다.

 

아래 사진을 보면 ASCII코드 영역에 “This Program cannot be run in DOS mode”라는 문자열을 볼 수 있다. 이 뜻은 DOS 모드에서 해당 파일이 실행되는 것을 막기 위한 것이다. 해당 부분은 16비트 환경에서만 실행되어 해당 문구를 확인할 수 있지만 32비트 환경에서는 실행되지 않는다고 한다.

 

 

3. NT Header

NT Header의 구조체인 IMAGE_NT_HEADER 32 bit 64 bit에서 사용하는 구조체가 다르다. 두 구조체의 차이점은 OptionalHeader 32 bit에서는 IMAGE_IPTIONAL_HEADER32 64 bit에서는 IMAGE_OPTIONAL_HEADER64를 사용한다는 점이다. 32bit 구조체의 크기는 248 byte이고 64 bit의 구조체의 크기는 264 byte.

 

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


typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

#ifdef _WIN64
typedef IMAGE_NT_HEADERS64        IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS64         PIMAGE_NT_HEADERS;
#else
typedef IMAGE_NT_HEADERS32          IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS32         PIMAGE_NT_HEADERS;
#endif

 

▶ DWORD Signature : 이 시그니처로 해당 파일이 PE 파일 구조인지 아닌지 알 수 있다. 시그니처는 PE00으로 50 45 00 00으로 저장된다. 

 

 

 

▶ IMAGE_FILE_HEADER

  아래 구조체에서 중요한 멤버만 보도록 하겠다.

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;


 > WORD Machine : CPU
에 따른 값으로 32 bit는 0x014, 64 bit는 0x8664이다. 이 외에도 어느 회사의 CPU인지도 알 수 있다. 

2447 #define IMAGE_FILE_MACHINE_UNKNOWN 0
2448 #define IMAGE_FILE_MACHINE_I860 0x014d
2449 #define IMAGE_FILE_MACHINE_I386 0x014c
2450 #define IMAGE_FILE_MACHINE_R3000 0x0162
2451 #define IMAGE_FILE_MACHINE_R4000 0x0166
2452 #define IMAGE_FILE_MACHINE_R10000 0x0168
2453 #define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169
2454 #define IMAGE_FILE_MACHINE_ALPHA 0x0184
2455 #define IMAGE_FILE_MACHINE_SH3 0x01a2
2456 #define IMAGE_FILE_MACHINE_SH3DSP 0x01a3
2457 #define IMAGE_FILE_MACHINE_SH3E 0x01a4
2458 #define IMAGE_FILE_MACHINE_SH4 0x01a6
2459 #define IMAGE_FILE_MACHINE_SH5 0x01a8
2460 #define IMAGE_FILE_MACHINE_ARM 0x01c0
2461 #define IMAGE_FILE_MACHINE_THUMB 0x01c2
2462 #define IMAGE_FILE_MACHINE_ARMNT 0x01c4
2463 #define IMAGE_FILE_MACHINE_AM33 0x01d3
2464 #define IMAGE_FILE_MACHINE_POWERPC 0x01f0
2465 #define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1
2466 #define IMAGE_FILE_MACHINE_IA64 0x0200
2467 #define IMAGE_FILE_MACHINE_MIPS16 0x0266
2468 #define IMAGE_FILE_MACHINE_ALPHA64 0x0284
2469 #define IMAGE_FILE_MACHINE_MIPSFPU 0x0366
2470 #define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466
2471 #define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
2472 #define IMAGE_FILE_MACHINE_TRICORE 0x0520
2473 #define IMAGE_FILE_MACHINE_CEF 0x0cef
2474 #define IMAGE_FILE_MACHINE_EBC 0x0ebc
2475 #define IMAGE_FILE_MACHINE_AMD64 0x8664
2476 #define IMAGE_FILE_MACHINE_M32R 0x9041
2477 #define IMAGE_FILE_MACHINE_CEE 0xc0ee

  

 > WORD NubmerOfSection : 섹션의 개수를 나타낸다. 항상 0보다 커야 하고 정의된 섹션의 개수와 동일해야 한다. 우리는 이 값으로 테이블 크기를 알 수 있다.

 

 > WORD SizeOfOptionalHeader : IMAGE_OPTIONAL_HEADER 구조체의 크기를 나타내며 32 bit 0xE0(224 byte)이고 64 bit 0xF0(240 byte)을 값으로 갖게 된다.

  * 해당 말은 즉, IMAGE_OPTIONAL_HEADER의 크기는 변경이 가능하다는 것이다.

 

 > WORD Characteristics : 파일의 정보를 나타내는 플래그고 bit OR 형식으로 조합되어 있다.

2429 #define IMAGE_FILE_RELOCS_STRIPPED 0x0001 /* No relocation info */
2430 #define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002
2431 #define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004
2432 #define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008
2433 #define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010
2434 #define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020
2435 #define IMAGE_FILE_16BIT_MACHINE 0x0040
2436 #define IMAGE_FILE_BYTES_REVERSED_LO 0x0080
2437 #define IMAGE_FILE_32BIT_MACHINE 0x0100
2438 #define IMAGE_FILE_DEBUG_STRIPPED 0x0200
2439 #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400
2440 #define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800
2441 #define IMAGE_FILE_SYSTEM 0x1000
2442 #define IMAGE_FILE_DLL 0x2000
2443 #define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000
2444 #define IMAGE_FILE_BYTES_REVERSED_HI 0x8000

 

 

 

▶ IMAGE_OPTIONAL_HEADER64

< 32 bit >

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;

 

< 64 bit >

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;

 

Optional Header 역시 중요한 멤버 10개만 보도록 하겠다.

 > WORD Magic : 32bit 구조체라면 0x10B이고 64bit 구조체라면 0x20B0이다

 

 > DWORD AddressOfEntryPoint : EPRVA 주소 값이 들어있어 프로그램의 시작 주소를 나타낸다

 

 > ImageBase : 가상 메모리에서 PE 파일이 시작되는 주소로 PE 파일이 맵핑되는 시작 주소를 가리킨다

 

 > DWORD SectionAlignment/FileAlignment : 메모리와 파일 섹션의 최소 단위로 섹션의 크기는 이 둘의 배수여야 한다

 

 > DWORD SizeOfImage : 가상 메모리에서 PE Image가 차지하는 크기로 보통 파일과 메모리의 로딩 크기는 다르다

 

 > DWORD SizeOfHeader : PE Header의 전체 크기로 FileAlignment의 배수여야 한다

 

 > DWORD Suabsystem : 파일을 구분해준다 (1 : 드라이버 파일, 2 : GUI 파일, 3 : CUI 파일)

 

 > DWORD NumberOfRvaAndSizes : DataDirectory의 배열 개수를 나타낸다

 

 > IMAGE_DATA_DIRECTORY DataDirectory : IMAGE_DATA_DIRECTORY 구조체 배열로 각 항목마다 정의된 값을 갖는다.

Offset (PE/PE32+) Description
96/112 Export table address and size
104/120 Import table address and size
120/136 Exception table address and size
128/144 Certificate table address and size
136/152 Base relocation table address and size
144/160 Debugging information starting address and size
152/168 Architecture-specific data address and size
160/176 Global pointer register relative virtual address
168/184 Thread local storage (TLS) table address and size
176/192 Load configuration table address and size
184/200 Bound import table address and size
192/208 Import address table address and size
200/216 Delay import descriptor address and size
208/224 The CLR header address and size
216/232 Reserved

 

 

 

4. Section Header

각 섹션의 속성을 정의한 것으로 프로그램의 안정성을 위해 섹션마다 엑세스 권한이 달라야 한다. 따라서 각각의 섹션의 속성을 기술할 섹션 헤더가 필요하다. 이 속성을 통해 .text(code) 섹션, .data 섹션, .rdata 섹션 등에 대한 정보를 알 수 있다.

 

섹션명 용도 
.text (code) 코드, 실행, 읽기 속성으로 컴파일 이곳에 저장
.data 초기화, 읽기, 쓰기 속성으로 초기화된 전역 변수를 저장
.rdata 초기화, 읽기 속성으로 const 선언된 변수같이 읽기만 가능한 데이터 저장
.bss 비초기화, 읽기, 쓰기 속성으로 초기화되지 않은 전역 변수 저장
.edata 초기화, 읽기 속성으로 EAT 관련된 정보 저장
.idata 초기화, 읽기, 쓰기 속성으로 IAT 관련 정보 저장
.rsrc 초기화, 읽기 속성으로 리소스 저장

 


DWORD VirtualSize : 메모리에 섹션이 차지하는 크기로 SectionAlignment의 배수다 

 

DWORD VirtualAddress : 메모리에서 섹션이 시작되는 주소로 RVA를 의미한다

 

DWORD SizeOfRawData : 파일에서 섹션이 차지하는 크기로 해당 섹션의 빈 공간이 얼마나 있는지 알기 위해서 필요하다. 빈공간이 생기는 이유는 FileAlignment의 배수로 공간을 할당하기 때문이다.

 

DWORD PointerToRawData : 파일에서 섹션의 시작 주소로 FileAlignment의 배수 값을 갖는다

 

DWORD Charcteristics : 섹션의 속성으로 쓰기와 읽기 등을 변경할 수 있다

 

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;

 

 

 


wow64 fs redirection

wow64Windows on Windows 64 bit라는 뜻으로 윈도우는 32 bit 환경에서 만들어진 프로그램을 64 bit 환경에서 실행시킬 때 system32 폴더로 파일을 복사하면 제대로 실행시키기 위해 자동으로 syswow64로 리다이렉트 되어 복사하게 된다. 그래서 만약 32 bit 프로그램의 PE 헤더에 대해서 볼 때 이미 64 bit 환경에 맞춰 리다이렉트 되어 HxD로 보게 된다면 원래 PE 헤더와 조금 다르게 나온다.

하지만 PE Viewer로 확인하면 원래 32 bit 환경과 똑같은 결과로 확인할 수 있다. 이 이유는 PE Viewer는 실행되는 파일에 따라 어떤 환경의 서브시스템에서 실행해야 하는지 판단한 후 해당 플랫폼에서 실행할 수 있도록 환경을 구성하기 때문이다. 그래서 32 bit 프로그램을 64 bit 환경에서 본다 하더라도 PE Viewer가 해당 프로그램은 32 bit 환경의 서브시스템에서 실행되어야 판단하여 wow64를 이용해 32 bit 환경을 구성해준다.

 

만약 이를 처음부터 피하고 싶다면 아래와 같은 코드로 리다이렉션을 피할 수 있다.

PVOID OldValue;

Wow64DisableWow64FsRedirection(&OldValue);

CopyFile(szSourcePath, szTargetPath, false);

Wow64RevertWow64FsRedirection(OldValue);

 

< 참고 >
http://egloos.zum.com/paulownia/v/4594905
https://noenemy.tistory.com/372

댓글