오늘은 PE 구조에 대해 정리를 하려 한다 .
( 리버싱 핵심원리 속칭 나뭇잎 책 을 참고하여 정리함을 알린다. )
Reversecore 라는 저자의 블로그에도 정리가 잘 되어있다.
( 블로그로 이동합니다 . )
PE 파일에 대한 공부로 IAT , INT 의 관계와 DLL 이 로드되는 과정을 함께 알아갈 수 있다.
PE 파일은 Window 운영체제에서 사용되는 파일형식이다 .
P : Portable
E : Executable
어느 플렛폼에서든 실행이 가능한 파일이라 보면 되겠다.
( 원래는 서로다른 운영체제간의 호환을 위한거였지만, 현재는 윈도우에서만 사용한다. )
PE 파일의 종류로는
실행 계열 : EXE, SCR
라이브러리 계열 : DLL, OCX, CPL, DRV
드라이버 계열 : SYS, VXD
오브젝트 파일 계열 : OBJ
여기서 오브젝트 파일을 제외한 모든 파일들은 실행이 가능함.
오브젝트 파일도 PE 공식 스펙에선 PE파일로 간주함.
각각의 헤더 구조체의 대한 정의는
WinNT.h 파일에 정의되어있다.
메모장의 PE 구조를 살펴보자 .
HxD 툴을 이용해 파일의 헥스값을 읽어들였다.
우리가 쓰는 윈도우 메모장의 PE 구조이다 .
네모 친 부분을 살펴보자.
모든 PE 파일들은 처음에 이러한 형태의 파일 구조를 띄고있다.
맨처음 시작은 MZ 로 시작하며 NT헤더의 시작은 PE 라는 문자로 시작한다 .
조금 더 자세히 알아보자.
( 밑의 그림을 잘 봐둬야한다 !!!! )
기본적으로는 DOS header 부터 Section header 까지를 PE 헤더라 부르고,
그 밑의 Section 들을 합쳐 PE 바디라고 부른다 .
파일형태 ( 왼쪽 ) 에서는 offset 으로,
메모리 ( 오른쪽 ) 에서는 VA ( Virtual Address ) 로 주소를 표현한다 .
즉, 파일이 메모리에 로딩되는 순간 섹션의 크기나 위치가 달라지게 된다 .
( 중요함 . )
각각의 섹션 헤더에는 저 섹션의 크기나 위치, 속성등의 내용이 들어가있고,
각 섹션의 사이사이 들어가있는 NULL 부분을 NULL-Padding 영역이라 부른다.
코드엔진의 자료에서 캡쳐하였다 .
각각의 헤더에 대한 정보들을 정리한다 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; | cs |
맨 처음에 해당하는 DOS 헤더의 내용이다.
헤더의 크기는 64바이트로 구성되어 있으며,
( 멤버중에 배열멤버가 있으니 꼭 확인하고 넘어가도록 하자 !! )
이것저것 굉장히 많지만 우리는 딱 두가지만 보면된다.
e_magic 멤버와 e_lfanew 라는 멤버만 확인하자 .
1. e_magic 멤버는 DOS Hader 의 signature 값이다 .
2. f_lfanew 멤버는 NT 헤더의 주소를 가리킨다.
윈도우 XP에서 notepad.exe 를 확인해보도록 하겠다.
e_mmagic 멤버는 항상 4D5A 로 MZ 를 표기한다.
e_lfanew 멤버는 다음 NT 헤더의 주소를 가리킨다.
( 0x000000E0 )
나머지 멤버는 현재는 쓰이지 않는 멤버들이니 확인만 하고 넘어가자 .
그리고 바로 밑의 ( 0x00000040 ~ 0x000000DF ) 까지의 영역은
DOS STUB 영역이며 16비트 어셈블리로 구성되어 있다.
이 영역이 없어도 정상실행이 되며, 32비트 윈도우 환경에선 명령어가 실행도 되지 않으며
PE 파일로 인식해 이 영역을 건너뛰고 NT Header 로 건너뛰게 된다 .
자 이제 본격적인 PE 의 시작 NT Header 를 보도록 하자.
얘는 좀 복잡하다...
1 2 3 4 5 | typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; | cs |
NE Header 를 찾아보면 DWORD 로 시그네쳐값이 들어가있다.
PE\0\0 으로 되있는 값이며 이값은 DOS Header 의 e_magic 값과 똑같이 변하지 않는다.
두번째는 구조체로 이뤄져있는데 FileHeader 의 구조를 보도록 하자.
1 2 3 4 5 6 7 8 9 | 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; | cs |
구조체의 크기는 20바이트이며, 파일헤더에선 4가지의 멤버를 주의깊게 봐야한다.
( 이 네가지의 멤버들이 정확한 값을 가지고 있지 않다면 정상적으로 실행이 되지 않는다. )
Machine , NumberOfSections , SizeOfOptionalHeader , Characteristics
Machine 멤버는 CPU 별로 고유값들이며 WinNT.h 파일에 정의되어 있다.
NumberOfSections 멤버는 섹션의 갯수를 가지고 있으며 이 값은 반드시 0보다 커야하며,
실제 섹션의 개수와 이 값이 다르다면 실행에러가 발생한다.
SizeOfOptionalHeader 멤버는 NT 헤더의 세번째멤버 OptionalHeader 의 사이즈를 가지고 있다 .
PE 로더는 이 멤버를 읽어들여 구조체의 크기를 인식하게 된다 .
( 32비트와 64비트 호환을 위한 멤버 )
Characteristics 멤버는 파일의 속성을 나타내는 값이다. 실행이 가능한 파일인지 DLL 파일인지 등의 정보가
비트형식으로 저장이 되어있다 .
( WinNT.h 파일에 각각의 값들이 정의되어 있다 . )
이번에는 PE 헤더 구조체중 가장 큰 크기를 가진 IMAGE_OPTIONAL_HEADER32 를 살펴보자 .
( 뒤의 숫자는 각 시스템 비트를 뜻한다 ! )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // 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; | cs |
크기도 클뿐더러 봐야할 멤버도 많다.
하나씩 차근차근 알아보자 .
Magic
IMAGE_OPTIONAL_HEADER32 구조체의 경우 10B
IMAGE_OPTIONAL_HEADER64 구조체의 경우 20B 로 세팅된다.
AddressOfEntryPoint
EP 의 RVA 값을 가지고 있다 .
프로그램의 시작실행주소를 가지고 있는 멤버이다.
ImageBase
프로세스의 가상 메모리는 0~FFFFFFFF 범위 (32비트의 경우)이며
이 멤버는 PE 파일이 로딩되는 시작주소를 나타낸다.
EXE, DLL 파일은 유저메모리 영역인 0~7FFFFFFF 범위에 로딩이 되며
시스템파일은 커널메모리 영역인 80000000 ~ FFFFFFFF 범위에 로딩이 된다.
일반적인 컴파일 도구들이 빌드한 EXE 파일의 ImageBase 값은 0x00400000 값을 가지며,
DLL 파일의 경우 10000000 을 가진다.
PE로더는 ImageBase + AddressOfEntryPoint 의 값을 EIP 로 세팅한다.
SectionAlignment , FileAlignment
메모리에서의 섹션의 크기와 파일에서의 섹션의 크기를 나타내는 변수이다.
파일에서 섹션의 최소단위가 FileAlignmennt ,
메모리에서 섹션의 최소단위가 SectionAlignment .
파일/메모리의 섹션의 크기는 반드시 각각 FileAlignment/SectionAlignment 의 배수가 되야 한다.
SizeOfImage
메모리에서 PE Image 가 차지하는 크기를 나타낸다.
( VA 는 절대주소, RVA 는 상대주소, RAW 는 파일주소이며,
메모리에 로딩될때 RAW 와 RVA 는 달라지게 된다. )
SizeOfHeader
PE 헤더의 전체 크기를 가지며, FileAlignment 의 배수여야 한다.
파일 시작에서 SizeOfHeader Offset 만큼 떨어진 위치에 첫번째 섹션이 존재하게 된다.
SubSystem
이 값을 보고 시스템 드라이버파일인지, 일반 실행 파일인지 구분 할 수 있다.
1 Driver File 시스템드라이버
2 GUI 파일
3 CUI 파일
NumberOfRvaAndSizes
마지막 멤버인 DataDirectory 배열의 개수를 나타낸다 .
( 구조체의 정의에선 16으로 정의되어 있지만, 이값을 보고 배열의 크기를 인식하게 된다. 꼭 16개가 아닐 수도 있다는 뜻)
DataDirectory
DataDirectory 는 IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목마다 정의된 값을 가진다 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor | cs |
섹션헤더를 살펴보자 .
섹션의 속성을 정의해둔 것이 섹션헤더이다.
섹션헤더에는 파일이나 메모리에서의 시작위치나 크기, 엑세스 권한등이 들어있다 .
섹션헤더는 NT헤더 바로 다음에 위치하며 구조체 배열 형태로 이루어져 있다.
그럼 섹션헤더의 모양을 먼저 살펴보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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; | cs |
Name
8바이트로 구성되며 섹션의 시그네쳐값이다 .
VirtualAddress
메모리에서 섹션의 시작주소이다 (RVA)
SizeOfRawData
이름에서부터 알것같다.
파일상태에서 섹션의 크기를 나타낸다.
PointerToRawData
파일에서 섹션영역의 시작 위치를 나타낸다.
RVA -> Raw 변환이나
RAW -> RVA 변환 시 사용하며
변환은 나중에 알아보자
Characteristics
섹션의 속성을 나타내며 OR 연산을 통해 여러 속성값을 받을 수 있다.
속성값들은 밑과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | // Section characteristics. // // IMAGE_SCN_TYPE_REG 0x00000000 // Reserved. // IMAGE_SCN_TYPE_DSECT 0x00000001 // Reserved. // IMAGE_SCN_TYPE_NOLOAD 0x00000002 // Reserved. // IMAGE_SCN_TYPE_GROUP 0x00000004 // Reserved. #define IMAGE_SCN_TYPE_NO_PAD 0x00000008 // Reserved. // IMAGE_SCN_TYPE_COPY 0x00000010 // Reserved. #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_LNK_OTHER 0x00000100 // Reserved. #define IMAGE_SCN_LNK_INFO 0x00000200 // Section contains comments or some other type of information. // IMAGE_SCN_TYPE_OVER 0x00000400 // Reserved. #define IMAGE_SCN_LNK_REMOVE 0x00000800 // Section contents will not become part of image. #define IMAGE_SCN_LNK_COMDAT 0x00001000 // Section contents comdat. // 0x00002000 // Reserved. // IMAGE_SCN_MEM_PROTECTED - Obsolete 0x00004000 #define IMAGE_SCN_NO_DEFER_SPEC_EXC 0x00004000 // Reset speculative exceptions handling bits in the TLB entries for this section. #define IMAGE_SCN_GPREL 0x00008000 // Section content can be accessed relative to GP #define IMAGE_SCN_MEM_FARDATA 0x00008000 // IMAGE_SCN_MEM_SYSHEAP - Obsolete 0x00010000 #define IMAGE_SCN_MEM_PURGEABLE 0x00020000 #define IMAGE_SCN_MEM_16BIT 0x00020000 #define IMAGE_SCN_MEM_LOCKED 0x00040000 #define IMAGE_SCN_MEM_PRELOAD 0x00080000 #define IMAGE_SCN_ALIGN_1BYTES 0x00100000 // #define IMAGE_SCN_ALIGN_2BYTES 0x00200000 // #define IMAGE_SCN_ALIGN_4BYTES 0x00300000 // #define IMAGE_SCN_ALIGN_8BYTES 0x00400000 // #define IMAGE_SCN_ALIGN_16BYTES 0x00500000 // Default alignment if no others are specified. #define IMAGE_SCN_ALIGN_32BYTES 0x00600000 // #define IMAGE_SCN_ALIGN_64BYTES 0x00700000 // #define IMAGE_SCN_ALIGN_128BYTES 0x00800000 // #define IMAGE_SCN_ALIGN_256BYTES 0x00900000 // #define IMAGE_SCN_ALIGN_512BYTES 0x00A00000 // #define IMAGE_SCN_ALIGN_1024BYTES 0x00B00000 // #define IMAGE_SCN_ALIGN_2048BYTES 0x00C00000 // #define IMAGE_SCN_ALIGN_4096BYTES 0x00D00000 // #define IMAGE_SCN_ALIGN_8192BYTES 0x00E00000 // // Unused 0x00F00000 #define IMAGE_SCN_ALIGN_MASK 0x00F00000 #define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 // Section contains extended relocations. #define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // Section can be discarded. #define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 // Section is not cachable. #define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 // Section is not pageable. #define IMAGE_SCN_MEM_SHARED 0x10000000 // Section is shareable. #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 writeable. | cs |
'Hacking > Reversing' 카테고리의 다른 글
RVA To Raw (RVA RAW 변환) (0) | 2015.10.23 |
---|