Search

pe 구조 분석

Tag
window
Create time
2020/04/20

1. PE Format

1.1 정의

PE 포맷(Portable Executable)은 윈도우 운영 체제에서 사용되는 실행 파일을 뜻하며 대표적으로 exe 파일이 있다. 뿐만아니라 아래와 같은 다양한 종류의 pe파일이 있다.
해당 pe파일은 ms에서 만들었으며, 파일이 실행되기 위한 정보나, 실행되는 환경같은 것을 분석을 통해 확인이 가능하다.
이러한 여러가지 정보를 확인이 가능하기 때문에 악성코드를 분석함에 있어 해당 정보를 이용해야한다.

1.2 종류

실행 계열 : .exe, .scr
드라이버 계열 : .sys, .vxd
라이브러리 계열 : dll, .ocx, .cpl, .drv
오브젝트 계열 : .obj

1.3 구조

PE파일은 크게 PE 헤더와 PE바디로 구성된다. PE 헤더는 파일을 실행하기 위한 전반적인 정보가 구조체 형식으로 저장되어 있고, PE 바디부분에는 파일의 실제 코드, 데이터, 리소스 등의 내용들이 존재한다.

2. PE Header

2.1 DOS Header

Microsoft는 PE파일 포맷을 만들때 DOS파일에 대한 하위 호환성을 고려해서 만들었다.
그 결과로 PE헤더 제일 앞부분에는 기존 DOS EXE Header를 확장시킨 IMAGE_DOS_HEADER구조체가 존재한다.
즉 IMAGE_DOS_HEADER는 DOS EXE Header를 확장신킨 것이라고 보면 된다.
모든 PE 파일의 e_magic에는 DOS 시그니처가 존재하며, e_lfanew 값이 가리키는 위치에 NT header 구조체가 존재한다. 그 구조체의 이름이 IMAGE_NT_HEADERS이다
<IMAGE_DOS_HEADER 구조체>
<PEVIEW>
IMAGE_DOS_HEADER 구조체 크기 : 40
e_magic 멤버변수 : DOS 시그니처 ( 4D5A(16진수) == "MZ"(아스키)) ⇒ 2byte
e_lfanew 멤버변수 : NT header의 시작주소를 표시(파일에 따라 가변) ⇒ 4byte

2.2 DOS Stub

DOS Header 다음에 나오는 부분으로 크게 중요한 부분이 아니다. 옵션이라고 생각하면 되며 크기는 가변이다.
즉, DOS 모드에서 실행시, 안내창 같은 역할을 해주는 추가적인 부분이다
<PEVIEW>
파란색으로 칠한 부분이 DOS stub이며 문자열 부분을 보면 This is program cannot be run in DOS mode 라고 써져있다. 해당 프로그램은 DOS 모드에서 실행할 수 없다는것을 말하고 있다

2.3 NT Header

NT 헤더 구조체는 1개의 멤버변수와 2개의 구조체로 구성된다
1. Signature // 멤버변수 2. FileHeader 3. OptionalHeader
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; // PE Signature IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
Plain Text
복사
Signature
NT Header 구조체의 시작을 알리며 아스키 값 PE로 시작한다
(** 항상 50 45 00 00 4바이트로 고정임)
<PEVIEW>
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;
Plain Text
복사
1.
Machine
해당 파일이 동작 가능한 머신의 종류를 코드에 담고 있으며 코드의 내용별 의미는 WinNT.h에 정의되어 있다. 쉽게 말하면 CPU 별로 고유한 값을 가지고 있다. 다음 아래의 그림이 해당 값이다.
#define IMAGE_FILE_MACHINE_UNKNOWN 0 #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386. #define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian #define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian #define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian #define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2 #define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP #define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian #define IMAGE_FILE_MACHINE_SH3DSP 0x01a3 #define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian #define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian #define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5 #define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian #define IMAGE_FILE_MACHINE_THUMB 0x01c2 #define IMAGE_FILE_MACHINE_AM33 0x01d3 #define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian #define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1 #define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64 #define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64 #define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS #define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS #define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64 #define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon #define IMAGE_FILE_MACHINE_CEF 0x0CEF #define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) #define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian #define IMAGE_FILE_MACHINE_CEE 0xC0EE
Plain Text
복사
2. NumberOfSections
현재 PE파일이 가지고 있는 Section 개수를 의미하며, 최소 1개 이상이여야 한다.
3. TimeDateStamp
파일을 빌드한 시간을 나타내는 값이다. 날짜가 타임스탬프 형식으로 기록되는데, 이는 확실히 신뢰할 수 있는 값이 아니고 변조가 가능하다. 따라서 대략적인 값으로 보면 된다.
<PEVIEW>
노란색으로 칠한 부분이 타임스탬프를 뜻한다. 16진수로 0x4F12C8F5인데 이를 10진수로 바꾸면 1326631157 이다. 이를 다시 표준 시각으로 바꾸면, 2012년 January 15일 Sunday PM 12:39:17 에 만들어 진 파일인 것을 알 수 있다.
→변환 사이트
4. SizeOfOptionalHeader
이 다음에 설명할 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 나타내는 값
→ IMAGE_NT_HEADERS안에 IMAGE_OPTIONAL_HEADER 32크기를 나타냄
<PEVIEW>
SizeOfOptionalHeader 필드의 값을 확인 할 수 있으며 값은 00E0으로 10진수로는 224바이트 만큼을 차지한다. 이 필드의 값은 32비트에선 0xE0, 64비트에서는 0xF0의 크기를 지닌다.
5. Charcteristics
PE 파일 속성에 대한 정보를 가지고 있으며 WinNT.h에 해당 설명이 자세히 나와 있다.
쉽게 말하자면 해당 필드는 현재 파일의 형식을 알려주는 역할을 하며, 이 필드의 값을 가지고 실행 가능한 파일인지, DLL 파일인지, 시스템 파일인지, 재배치 여부등에 대한 정보가 들어있다고 보면 된다.
<WinNT.h>
#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
Plain Text
복사
<PEVIEW>
Characteristic 필드의 값이 010F라는 것을 알 수 있으며, 이는 0001+0002+0004+0008+0100 합한 값과 같다. 위의 WinNT.h 사진을 보면 해당 값이 의미하는 것은 다음과 같다.
0001 : IMAGE_FILE_RELOCS_STRIPPED // 해당 파일에서 재배치 정보가 삭제됨 0002 : IMAGE_FILE_EXECUTABLE_IMAGE // 해당 파일은 실행가능한 EXE 파일임 0004 : IMAGE_FILE_LINE_NUMS_STRIPPED // 해당 파일은 라인 넘버가 제거됨 0008 : IMAGE_FILE_LOCAL_SYMS_STRIPPED // 해당 파일은 로컬 심볼 정보가 제거됨 0100 : IMAGE_FILE_32BIT_MACHINE // 해당 파일은 32비트 머신을 필요로 함
IMAGE_OPTIONAL_HEADER 32
메모리에 올라갈 때 참조해야할 중요한 필드들이 위치하고 있다. 해당 구조체는 총 224bytes의 크기를 갖으며 많은 필드가 위치해 있다.
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;
Plain Text
복사
1.
Magic
32비트인 경우에는 값이 10B이며, 64비트인 경우에는 20B 값을 가진다. 시그니처라고 생각하면 된다.
<PEVIEW>
2.
MajorLinkerVersion과 MinorLinkVersion
본 파일을 만들어낸 링커의 버전을 뜻함
<PEVIEW>
3.
SizeofInitializedData, SizeofUnintializedData
각각 코드 섹션을 제외한 초기화된 데이터 섹션의 크기초기화 되지 않은 데이터 섹션의 크기를 나타난다.
4.
SizeOfCode
코드 영역의 전체 크기가 이곳에 들어간다. 즉 .text 섹션의 크기가 들어간다는 뜻이다.
<PEVIEW>
5.
AddressOfEntryPoint
로더가 실행을 개시할 주소를 나타낸다. 쉽게 말하자면 프로그램이 메모리에서 실행 되는 시작 지점이라고 보면된다. 이 부분을 바로 EntryPoint라고 말한다. 위치는 RVA 값으로 저장되어 있다.
EIP 레지스터(실제로 다음 실행할 명령이 들어가 있는 메모리의 번지를 가리키는)의 값을 파일이 메모리에 로딩되고나서 ImageBase+AddressOfEntryPoint로 지정한다.
이는 올리디버거 같은 디버거를 통해서 파일을 실행시키고 나면, 디버거가 처음 실행할 위치를 ImageBase+AddressOfEntryPoint로 잡는다
<PEVIEW>
위 그림에서 AddressOfEntryPoint의 값을 보면 00001188인 것을 확인 할 수 있다. 따라서 ImageBase + 00001188 의 값이 바로 EntryPoint라고 볼 수 있다.
ImageBase : 0x4———-ooooo00000
AddressOfEntryPoint : 0x1188
EntryPoint : 0x401188
그럼 실제 EntryPoint가 0x401188인지 올리디버거를 통해서 확인해보자.
실제 해당 프로그램을 올리디버거에서 실행시켰을 시 0x401188의 주소부터 시작되는 것이 확인된다.
6.
BaseOfData, BaseOfCode
각각 첫 번째 코드 섹션이 시작되는 RVA, 데이터 섹션이 시작되는 RVA를 의미한다.
BaseOfCode가 RVA이니 ImageBase + BaseOfCode의 값은 실제 코드 영역의 주소가 됨
<PEVIEW>
ImageBase : 0x400000
BaseOfCode : 0x1000
실제 코드 섹션 : 0x401000
7.
ImageBase
PE 파일이 메모리에 로드될 때의 시작주소를 가리킨다. 기본적으로 EXE 파일의 경우에는 0x400000 번지가 , DLL 파일인 경우에는 0x10000000 번지로 지정되어 있다. (항상 이 번지로 고정되는 것은 아님. 링커 옵션을 통해서 시작 주소를 지정할 수 있다.)
DLL의 경우는 기본 ImageBase의 값이 0x10000000번지로 지정되어 있지만, 다른 DLL이 이 번지를 차지하고 있을 경우에는 다른 곳에 배치되는 재배치가 이루어 진다.
이 ImageBase는 RVA의 기준이 되는데, RVA란 상대 가상 주소라는 개념으로 ImageBase로부터 얼마나 떨어져 있는지를 나타내는 일종의 오프셋 개념으로 생각하면 된다. 다만 RVA는 파일이 아닌 메모리 공간에서의 상대적인 값이다.
예를 들어 ImageBase가 0x400000 번지 일 경우, .text 섹션의 RVA 값이 0x3000이면 실제 .text 섹션이 로드되는 위치는 0x403000이 된다.
<PEVEIW>
해당 파일은 ImageBase 값이 400000 이므로 0x00400000이 시작 주소임을 알 수 있다. 즉 exe 파일인 것을 알 수 있다.
8.
SectionAlignment
메모리에서 섹션의 최소단위를 나타낸다. 메모리에서 섹션의 크기는 반드시 SectionAlignment의 배수가 되어야 한다.
<PEVIEW>
SectionAlignment의 값은 0x00001000이다. 이는 메모리 공간에서 섹션의 크기가 0x1000의 배수라고 할 수 있따. 섹션의 크기에서 구조체 크기를 제외한 빈 공간은 모두 0으로 채워지며 이것을 패딩이라고 한다
9.
FileAlignment
PE 파일에서 섹션의 최소 단위를 나타낸다. PE파일에서 섹션의 크기는 반드시 FileAlignment의 배수가 되어야 한다.
<PEVIEW>
10.
SizeOfImage
PE 파일이 메모리에 로딩되었을 때의 전체 크기를 담고 있다. 이 값은 파일의 크기와 같을 떄도 있으며, 다를 떄도 있으나 다른 경우가 더 많다. PE 파일이 메모리에 로딩되고 나서는 SectionAlignment의 영향을 받아 패딩이 따라붙으며, SizeOfImage 역시 SectionAlignmnet의 영향을 받는다고 할 수 있다.
즉, 이미지 실행을 위해 메모리를 할당해야 하는 총 크기를 뜻한다.
<PEVIEW>
SizeOfImage가 0xA000 이므로, 이는 PE 파일이 메모리에 로딩되었을 때의 전체 크기가 0xA000이라는 뜻이다.
11.
SizeOfHeaders
이름 그대로 모든 헤더의 크기를 담고 있다. 즉, 도스 헤더, 도스 스텁, PE 헤더, 섹션 헤더의 크기를 모두 더한 값이라고 할 수 있다. 파일의 시작점에서 SizeOfHeaders 만큼 떨어진 Offset에 첫번째 섹션이 존재한다.
<PEVIEW>
12.
IMAGE_DATA_DIRECTORY DataDirectory[..]
DataDirectory는 구조체 배열로, 배열의 각 항목마다 정의된 값을 가지게 된다. 각 항목은 아래 그림 처럼 가상 주소와 size 필드로 구성되어 있다.
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
Plain Text
복사
<DataDirectory[] 구조체 배열에 들어가는 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
Plain Text
복사
IMAGE_OPTIONAL_HEADER의 DataDirectory 필드는 익스포트 디렉터리, 임포트 디렉터리, 리소스 디렉터리, 예외 디렉터리, 보안 디렉터리 영역 등에 접근할 수 있는 주소와 크기를 지니고 있는 배열로, IMAGE_DATA_DIRECTORY 구조체의 VirtualAddress를 통해 가상 주소를 알 수 있으며, Size를 통해 크기를 알 수 있다.
(여기서 중요한 값은 EXPORT, IMPORT, RESOURCE, TLS, IAT임)
이미지 디렉토리 정보는 굉장히 중요하다. 경우에 따라서 섹션이 합쳐질 수 있기 때문에 통합된 섹션에서 원하는 정보를 찾는 방법은 이미지 디렉토리에 포함된 정보를 이용하는 방법 밖에는 없다. 여러모로 많이 쓰이는 인덱스는 아래와 같은 역할을 한다.
IMAGE_DIRECTORY_ENTRY_EXPORT : Export 함수들에 대한 Export Table의 시작 위치와 크기를 나타냄
IMAGE_DIRECTORY_ENTRY_IMPORT : Import 함수들에 대한 Import Table의 시작 위치와 크기를 나타냄
IMAGE_DIRECTORY_ENTRY_RESOURCE : IMAGE_RESOURCE_DIRECTORY 구조체의 시작 위치를 나타냄
IMAGE_DIRECTORY_ENTRY_TLS : Thread Local Storage에 대한 포인터
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG : IMAGE_LOAD_CONFIG_DIRECTORY 구조체애 대한 포인터
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT : IMAGE_BOUND_IMPORT_DESCRIPTOR 구조체의 배열을 가리키는 포인터
IMAGE_DIRECTORY_ENTRY_IAT : Import Address Table의 시작 위치를 나타냄
실제 위의 값중에서 변경하면 OS의 로더에 의해 로딩이 되지 않는 부분이 있는데, 붉은 색으로 표시된
IMAGE_DIRECTORY_ENTRY_TLS IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG
부분이다. 이 부분은 미리 로더가 읽어서 초기 작업을 실행하는 부분이라 이부분이 포함된 영역을 이상하게 조작하게되면 로더가 로딩에 실패하게 된다

2.4 IMAGE_SECTION_HEADER

PE 헤더의 뒷부분에 연속해서 IMAGE_SECTION_HEADER가 위치하게 된다. 섹션은 뒤에 올 코드나 데이터가 위치하는 영역에 대한 구체적인 정보를 포함하고 있으므로 굉장히 중요하다.
섹션의 개수는 앞서 에 포함된 에서 얻을 수 있으며 해당 개수만큼 얻어오면 된다.는 WinNT.h에 아래와 같이 정의되어있다.
IMAGE_FILE_HEADER
NumberOfSections
IMAGE_SECTION_HEADER
즉 이 섹션 헤더는 섹션 테이블이라고도 하며 해당 구조체는 섹션에 대한 정보를 관리하는 구조체라고 할 수 있다. 이 구조체를 가지고 .text 섹션이나, .data 섹션, .radata 섹션 등에 대한 정보를 알 수 있다.
#defineIMAGE_SIZEOF_SHORT_NAME 8 typedef struct _IMAGE_SECTION_HEADER { BYTEName[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;
Plain Text
복사
위 코드를 보면 필드가 총 11개로 이루어 진 것을 확인 할 수 있다. 해당 필드들은 .text, .data 등의 모든 섹션에 해당되는 내용이다. 따라서 지금부터 .text 영역을 가지고 이 중에서 중요한 몇개만 알아보겠다.
<.text 섹션 구조>
1.
Name
필드명 그대로 섹션의 이름을 나타낸다.IMAGE_SIZEOF_SHORT_NAME 의 값인 8은 섹션의 이름은 최대 8바이트까지 가능하다는 뜻이다. 기본적인 섹션의 이름에 대한 용도는 다음과 같다.
.text :
코드, 실행, 읽기 속성을 지니며 컴파일 후의 결과가 이곳에 저장된다. 즉, 이 섹션은 실행되는 코드들이 들어가는 섹션이다.
.data :
초기화, 읽기, 쓰기 속성을 지니며 초기화된 전역 변수를 가진다.
.rdata :
초기화, 읽기 속성을 지니며 문자열 상수나 const로 선언된 변수처럼 읽기만 가능한 읽기 전용 데이터 섹션이다.
.bss :
비초기화, 읽기, 쓰기 속성을 지니며 초기화되지 않은 전역 변수의 섹션이다.
.edata :
초기화, 읽기 속성을 지니며 EAT와 관련된 정보가 들어가 있는 섹션이다.
.idata :
초기화, 읽기, 쓰기 속성을 지니며 IAT와 관련된 정보가 들어가 있는 섹션이다.
.rsrc
초기화, 읽기 속성을 지니며 리소스가 저장되는 섹션이다.
2.
VirtualSize
PE 로더를 통해 PE 파일이 메모리에 로드되고 나서의 메모리에서 섹션이 차지하는 크기를 가진다. 위 그림에서 VirtualSizePhysicalAddress를 필드로 갖는 공용체가 존재하지만, 여기서 PhysicalAddress는 현재 사용되지 않는 필드고 VirtualSize 필드만 사용된다. Name 필드 다음부터 4바이트를 읽으면 FE 36 00 00이 되고, 이는 0x000036FE 이라는 값이 된다.
3.
VirtualAddress
PE 로더를 통해 PE 파일이 메모리에 로드되고 나서의 해당하는 섹션의 RVA 값 이다. 즉, RVA는 이미지 베이스를 기준으로 하는 것이기에 예를 들어서 ImageBase의 값이 0x400000이고, VirtualAddress의 값이 0x1000이라면 로더는 0x401000에 섹션을 올리게 된다.
ImageBase + VirtualAddress는 해당 섹션의 실제 주소값이라고 할 수 있다. 위 그림에서 VirtualSize 필드 다음부터 4바이트를 읽게 되면 00 10 00 00으로, 이는 00001000(0x1000)의 값이 된다.
4.
SizeOfRawData
파일 상에서의 해당 섹션이 차지하는 크기(Optional Header 구조체의 FileAlignment 값의 배수가 되도록 올림한 값)를 가진다. 이는 실제로 사용된 크기이며, 패딩을 제외한 크기라고 할 수 있다. 위 그림에서 VirtualAddress 필드 다음부터 4바이트를 읽게 되면 00 40 00 00으로, 이는 0x0004000의 값이 된다.
5.
PointerToRawData
파일 상에서의 해당 섹션이 시작하는 위치(파일 오프셋)를 담고 있다. 이 값 역시도 Optional Header 구조체의 FileAlignment 값의 배수가 되어야 하며, 위 그림에서 SizeOfRawData 필드 다음부터 4바이트를 읽게 되면 00 10 00 00으로, 이는 00001000(0x1000)이 된다.
6.
Characteristics
해당 영역의 속성을 나타내는데, WinNT.h에 정의되어있고 아주 흥미로운 값을 가지고 있다.
// 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.
Plain Text
복사
대표적으로 중요한 것들은 다음과 같다
#define IMAGE_SCN_CNT_CODE 0x00000020 // 코드로 채워진 섹션
#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 // 데이터가 초기화된 섹션
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 // 데이터가 비초기화된 섹션
#define IMAGE_SCN_MEM_EXECUTE 0x20000000 // 실행 가능한 섹션
#define IMAGE_SCN_MEM_READ 0x40000000 // 읽기가 가능한 섹션
#define IMAGE_SCN_MEM_WRITE 0x80000000 // 쓰기가 가능한 섹션
위 의 값을 보면 섹션에 대한 속성이 미리 정의되어있다는 것을 알 수 있다. 즉 데이터 섹션 같은 경우 IMAGE_SCN_MEM_READ/WRITE 속성을 가지고 있으리라 유추할 수 있고, 코드가 포함된 섹션의 경우 IMAGE_SCN_MEM_EXECUTE 속성을 가지고 있다고 유추할 수 있다.
<PEVIEW>
.text 섹션에 대한 Characteristics 값을 보면 60000020 이라는 것을 알 수 있는데, 이는 한가지 속성이 아닌 여러가지 속성이 조합된 값을 가진다.
IMAGE_SCN_CNT_CODE : 00000020
IMAGE_SCN_MEM_EXECUTE : 20000000
IMAGE_SCN_MEM_READ : 40000000
위 값 들을 다 더하면 60000020 이다. 즉 해당 .text 섹션은 코드로 채워졌으며, 실행 가능한 섹션이고 읽을 수 있는 섹션을 뜻한다.
섹션의 경우 섹션 이름을 가지고 있는데, VC로 실행파일을 만들면 .text, .data, .idata와 같은 이름의 섹션들이 생긴다. 이름 그대로 코드, 데이터와 같은 정보가 포함된 섹션이라는 것을 알 수 있는데, 여기서 속지 말아야 할 것은 섹션 이름은 권장값이므로 섹션 이름으로 섹션이 포함하는 내용을 판단하면 안된다는 것이다
특히 파일의 크기를 줄이는 릴리즈 옵션 같은 경우는 섹션들이 합쳐져서 하나의 섹션으로 존재하는 경우도 있기 때문에 섹션 이름을 이용해서 찾아서는 안되며 IMAGE_NT_HEADER 에 있는 Data Directory 의 값을 참조해서 찾도록 해야 한다.