본문 바로가기

리버스엔지니어링

PE 구조 정리-1

PE(Portable Executable)파일은 말 그대로 옮겨다니면서 실행시킬수 있는 파일을 말합니다.

Microsoft가 다른 운영체제와 이식성을 좋게 하기 위해서 만든 파일 포멧입니다.
그러면 Microsoft가 이런 PE파일을 만들기 위해 제작한 PE 파일 구조에 대해서 공부를 해보겠습니다.

1. PE 파일
공식적으로 PE파일의 종류는
 실행 계열 : EXE, SCR 드라이버 계열 : SYS, VXD
 라이브러리 계열 : DLL, OCX, CPL, DRV 오브젝트 파일 계열 : OBJ
이렇게 OBJ를 제외한 나머지는 모두 실행 가능한 파일로 이루어져 있습니다.
2. 배우는 이유
PE파일 구조를 배우면 파일이 실행되기 위한 모든 정보를 얻을 수 있습니다.
예를 들어 어느 메모리의 주소에 로딩이 되는지, 프로그램이 사용하는 API의 정보 등 등을 얻을 수 있습니다.
3. PE 파일 구조





이런식으로 파일과 메모리에 영역이 구분되어서 PE파일들이 메모리에 맵핑이됩니다.
앞으로 이 PE구조 안에 있는 작은 DOS header, DOS stub 등 등을 공부해보겠습니다.

4. PE 헤더
PE헤더란 PE파일 구조중에서 DOS header부터 Section header까지를 말합니다.
그러면 PE헤더에 있는 각각의 부분에 대해서 가르쳐 드리겠습니다.
1) 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;

여기서 중요한 값은 e_magic,e_lfanew 이렇게 2개가 있습니다.

여기서 e_magic의 값은 DOS signature로 MZ라는 문자열입니다.

(MZ는 Mark Zbikowski라고 DOS실행파일을 설계한 사람의 이니셜입니다.)

또한 e_lfanew의 값은 NT header의 RVA형태의 주소값을 가지고있습니다.

(이 값은 꼭 40이상의 크기를 가질 필요는 없습니다.)


실제로 계산기를 HxD로 열어보니 이렇게 나오네요.


2) DOS stub

DOS 에서 돌아갈 명령어가 16bit명령어 형태로 있습니다.

이 부분은 없어도 상관없고 크기도 정해져있지 않습니다.

여러 프로그램들을 분석해보았을때 보통 프로그램들은 This program cannot be run in DOS mode를 출력하고 꺼지게 만듭니다.


실제로 계산기도 그렇게 표시되고 꺼지게 되있네요.


3) NT headers


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;

NT header 구조체는 이렇게 크게 3개로 구분되 있습니다.

맨 첫번째 로는 Signature인데 이것은 PE라는 문자를 나타냅니다.

DOS signature과 마찬가지로 이 문자를 보면 아 이부분이 NT header의 시작이구나 하고 넘어갑시다.


실제로도 이렇게 나오는데 여기서 부터 NT header구조체의 시작이구나 하면서 판별을 합시다.


2번째 로는 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

Machine은 CPU별로 고유한 값을 가지면서 IA-32 호환 CPU는 14Ch의 값을, IA-64 호환 CPU는 200h의 값을 가집니다.

2. NumberOfSections

이 값은 이 다음에 나오는 Section들의 갯수입니다.

최소 1개 이상이여야 됩니다.

3. SizeOfOptionalHeader

이 값은 IMAGE_NT_HEADERS구조체의 마지막구조체 IMAGE_OPTIONAL_HEADER32의 구조체의 크기를 나타냅니다.

( 이 값과 e_lfanew값의 범위를 이상하게 지정할 수 있다는 것 때문에 변종 PE를 만들어 낼 수 있습니다.) 

4. Characteristics

이 값은 드디어 맨처음부터 말한 이 파일의 속성에 대한 부분입니다.

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable  
                                                     // (i.e. no unresolved externel references).

#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from 
                                                     // file in .DBG file

#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, 
                                                     // copy and run from the swap file.

#define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net, 
                                                     // copy and run from the swap file.

#define IMAGE_FILE_SYSTEM                    0x1000  // System File.
#define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.

이렇게 많은 속성들이 있는데 이 파일에 해당하는 부분들만 bit OR형식으로 조합되서 Characteristics값에 저장이 됩니다.
여기서 중요한것은 당연히 색칠된 2와 2000값이겠지요.

실제로는 이렇게 나오며 여기있는 부분을 해석하면

Machine : 014C (=IA-32호환)

NumberOfSections : 4 (text,data,rsrc,reloc)

SizeOfOptionalHeader : 00E0

Characteristics : 0102 (IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_32BIT_MACHINE)

이렇게 파일들에 대한 정보를 얻어 낼 수 있습니다.



이 다음에는 마지막 구조체인 IMAGE_OPTIONAL_HEADER32입니다.

typedef struct _IMAGE_DATA_DIRECTORY {

    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

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용이라면 10Bh의 값을,64bit용이라면 20Bh의 값을 가지게 됩니다.

2. AddressOfEntryPoint

EP의 RVA 주소값이 들어있습니다.

물론 RVA값이니까 실제 EP는 AddressOfEntryPoint+ImageBase이겠죠

3. ImageBase

PE파일이 맵핑되는 시작 주소를 가리킵니다.

4. SectionAlignment

메모리에서의 섹션의 최소단위를 나타냅니다.

5. FileAlignment

파일에서의 섹션의 최소단위를 나타냅니다.

4,5번의 최소단위를 맞추기 위해서 PE구조에서 각 section들이 끝나면 NULL값을 집어넣는 NULL padding이란 기술을 사용합니다.

6. SizeOfImage

메모리에서의 PE구조의 크기를 나타냅니다.

7. SizeOfHeader

PE header의 크기를 나타냅니다.

8. Subsystem

1의 값을 가지면 드라이버 파일(SYS, VXD)파일이란 뜻입니다.

2의 값을 가지면 GUI파일이란 뜻입니다.

3의 값을 가지면 CUI파일이란 뜻입니다.

9. NumberOfRvaAndSizes

바로 밑에 있는 IMAGE_DATA_DIRECTORY구조체의 배열의 크기를 정합니다.

(맨 처음에 #define으로 16이라고 크기가 정해져있지만

windows에서는 이 값을 보고 구조체 배열의 크기를 정합니다.)


실제 IMAGE_OPTIONAL_HEADER32 구조체 입니다.

분석을 해보면

Magic : 010B (=32bit)

Address of Entry point : 00012D6C

Imagebase : 01000000

Section Alignment : 1000

File Alignment : 200

Size of Image : 0C0000

Size of Headers : 400

Subsystem : 2

Number of Directoris : 10

이렇게 값이 나오는 것을 알 수 있습니다.

(Number of Directoris 왜 10이냐고 16이 아니냐고 하면 10h 는 16입니다.)


일단 이정도로 PE 구조 정리-1 을 끝내겠습니다.

읽어주셔서 감사합니다.

'리버스엔지니어링' 카테고리의 다른 글

간단한 리버싱 워게임-1  (0) 2014.04.11
reversing.kr 44위 달성!  (0) 2014.01.27
PE 구조 정리-2  (0) 2013.07.21
레지스터 정리  (2) 2013.07.11