ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리버싱 핵심원리 study] 13장 PE File Format (1)
    Reverse Engineering 2020. 12. 25. 01:44

    PE(Portable Executable) 파일은 Windows 운영체제에서 사용되는 실행 파일 형식이다. 기존 UNIX의 COEF(Common Object File Format)을 기반으로 Microsoft에서 만들었으며, 문자 그대로 이식성을 좋게 하려는 의도가 있었으나 Windows 운영체제에서만 사용된다.

     

    notepad.exe(32bit)을 헥스 에디터 HxD를 이용하여 열어보면 다음과 같다.

    notepad.exe

    위 그림은 notepad.exe 파일의 시작 부분이며, PE 파일의 header 부분이다. 헤더 부분에는 파일이 실행되기 위해 필요한 모든 정보들이 있으며 구조체 형식으로 저장되어 있다. PE File Format을 공부한다는 것은 PE 헤더 구조체를 공부한다는 것과 같은 말이다.

     

    1. 기본 구조

    아래 그림은 notepad.exe 파일이 메모리에 loading 될 때의 모습을 나타낸다.

     

    PE 파일이 메모리에 loading 되었을 때의 모습

    DOS header부터 Section header까지를 PE 헤더, 밑의 Section들을 합쳐 PE 바디라고 한다. 파일에서는 offset으로, 메모리에서는 VA(Virtual Address)로 위치가 표현되며 파일이 메모리에 로딩되면 모양이 달라진다. Section header에는 각 Section에 대한 파일/메모리에서의 크기, 위치, 속성 등이 정의되며 각 section의 끝에는 NULL padding이라고 불리는 영역이 존재한다.

     

    VA와 RVA에 대해 구분할 줄 알아야 하는데, VA(Virtual Address)는 프로세스 가상 메모리의 '절대 주소'를 말하며, RVA(Relative Virtual Address)는 어느 기준 위치(ImageBase)에서부터의 '상대 주소'를 가리킨다. 즉 다음과 같은 수식으로 나타난다.

    RVA + ImageBase = VA

    이는 프로세스가 메모리에 로딩되는 순간 이미 다른 파일이 로딩되어있는 경우, relocation을 통해 다른 위치에 로딩하기 위한 것이다.

     

    2. PE 헤더

    1) DOS Header

    PE File Format을 만들 당시, 널리 사용되던 DOS 파일에 대한 하위 호환성을 고려하여 만들어진 부분이다. 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

    구조체의 크기는 40이며, 중요한 멤버로 e_magic과 e_lfanew가 있다.

    e_magic : DOS의 signature를 나타내는 부분으로, MZ값이 와야한다.

    e_lfanew : NT Header 구조체의 offset을 나타낸다. HxD에서 해당 부분을 살펴보면 다음과 같다.

    IMAGE_DOS_HEADER

    MZ의 아스키 값인 4D5A가 e_magic 부분에 나타나고, 000000E0 (little endian임에 주의)가 e_lfanew 값으로 나타나는 것을 확인할 수 있다.

     

    2) DOS Stub

    DOS Header 밑에는 DOS Stub이 존재한다. Optional 한 영역으로 없어도 되며 NT Header가 시작되기 이전까지의 영역이다. (없어도 된다고 해서 없애버리면 프로그램이 실행이 안된다...e_lfanew offset을 올바르게 잡아줘도 file 크기를 나타내는 값 등 전체적인 틀이 깨져 다른 멤버의 수정도 필요한 듯하다.) 해당 부분은 DOS용 실행 부분으로, 16bit 어셈블리 명령어로 구성되어 있다.  HxD에서 해당 부분을 살펴보면 다음과 같다.

    DOS Stub

     

    3) NT Header

    NT header 구조체는 다음과 같다.

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

    위와 같이 3개의 멤버로 되어 있는데 첫 멤버는 Signature로 5045000h("PE"00) 값을 갖는다. 그리고 FileHeader와 Optional Header 구조체 멤버가 있다. 우선 Signature에 해당되는 부분을 HxD에서 살펴보면 다음과 같다.

    IMAGE_NT_HEADERS

     

    4) NT Header - File Header

    파일의 개략적인 속성을 나타내는 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;

    IMAGE_FILE_HEADER 구조체에서는 다음과 같은 4가지 멤버가 중요하다.

     

    #1 Machine : CPU별로 고유한 값이 부여된다.

    #2 NumberOfSections : PE 파일에 존재하는 Section의 개수를 나타낸다.

    #3 SizeOfOptionalHeader : IMAGE_NT_HEADERS의 마지막 멤버인 IMAGE_OPTIONAL_HEADERS32 구조체의 크기를 나타낸다. C언어 구조체이기 때문에 크기가 이미 정해져 있지만, Windows PE로드는 이 값을 보고 구조체 크기를 인식한다.

    #4 Characteristics : 파일의 속성을 나타내는 값으로, 실행이 가능한 형태인지, DLL 파일인지 등의 정보가 OR 형식으로 조합되어 표기된다.

     

    이제 HxD에서 IMAGE_FILE_HEADER 구조체를 확인해보면 다음과 같다.

    IMAGE_FILE_HEADER

    중요 멤버만 살펴보면 다음과 같다.

    Machine : 014C (x86)

    NumberOfSections : 00003 (3개)

    SizeOfOptionalHeader : 00E0

    Characteristics : 010F

     

    5) 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 : 32bit용 구조체인 경우 10B, 64bit용 구조체인 경우 20B를 갖는다.

    #2 AddressOfEntryPoint : EP의 RVA 값을 나타낸다.

    #3 ImageBase : PE 파일이 로딩되는 시작 주소를 나타낸다.

    #4 SectionAlignment, FileAlignment : 각각 메모리에서의 최소 단위, 파일에서의 최소 단위를 나타낸다.

    #5 SizeOfImage : 가상 메모리에서 PE Image가 차지하는 크기를 나타낸다.

    #6 SizeOfHeader : PE 헤더의 전체 크기를 나타낸다.

    #7 Subsystem : 시스템 드라이버(*.sys)인지, 일반 실행 파일인지(*.exe, *.dll)를 구분한다.

    #8 NumberOfRvaAndSizes : IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타낸다.

    #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

    HxD에서 IMAGE_OPTIONAL_HEADER 구조체를 살펴보면 다음과 같다.

    IMAGE_OPTIONAL_HEADER

    위에서 언급한 중요 멤버 값만 정리해보면 다음과 같다.

    Magic : 010B (32-bit임을 나타낸다.)

    AddressOfEntryPoint : 0000739D (EP의 RVA)

    ImageBase : 01000000 (PE 파일이 로딩되는 시작 주소)

    SectionAlignment : 00001000

    FileAlignment : 00000200

    SizeOfImage : 00014000

    SizeOfHeader : 00000400

    Subsystem : 0002 (창 기반 애플리케이션을 나타냄)

    NumberOfRvaAndSizes : 00000010 (16개의 DataDirectory가 있음을 나타냄)

     

    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;

    알아야 할 중요 멤버는 다음과 같다.

     

    #1 VirtualSize : 메모리에서 섹션이 차지하는 크기

    #2 VirtualAddress : 메모리에서 섹션의 시작 주소(RVA)
    #3 SizeOfRawData : 파일에서 섹션이 차지하는 크기

    #4 PointerToRawData : 파일에서 섹션의 시작 위치

    #5 Characteristics : 섹션의 속성(bit OR)

    이 중 VirtualAddress와 PointerToRawData는 IMAGE_OPTIONAL_HEADER32에 정의된 Alignment를 따라야 한다. 또한 파일 상태에서의 섹션의 크기와, 메모리에 로딩되었을 때의 섹션의 크기가 서로 다른 경우가 일반적이므로, VirtualSize와 SizeOfRawData는 서로 다른 값을 갖는다. 멤버 중 Name에는 따로 특별한 규칙이 없기 때문에 어떠한 값이 와도 상관없다. (메모장의 경우 .text, .data, .rsrc가 온다.)

     

    HxD에서 IMAGE_SECTION_HEADER를 살펴보면 다음과 같다. (3개의 섹션이 존재하므로 3 부분이 있다.)

    IMAGE_SECTION_HEADER

    Name 멤버를 기준으로 3개(.text / .data / .rsrc)로 나뉜다.

    반응형

    댓글

Designed by Tistory.