PE 파일이란?
PE(Portable Executable) 파일은 Windows 운영체제에서 사용되는 실행 파일 형식이다.
단어 그대로 옮겨다니면서 실행시킬 수 있는 파일을 뜻한다.
PE 파일은 32비트 형태의 실행 파일을 의미하며 PE32라는 용어를 사용하기도 한다.
64비트 형태의 실행파일은 PE64...가 아니라 PE+ 또는 PE32+라고 부른다.
PE 파일의 종류는 다음과 같다.
실행 계열 : EXE, SCR
드라이버 계열 : SYS, VXD
라이브러리 계열 : DLL, OCX, CPL, DRV
오브젝트 파일 계열 : OBJ
(PE 공식 스펙에는 컴파일 결과물인 OBJ(오브젝트) 파일도 PE 파일로 간주한다. 하지만 OBJ 파일 자체로는 어떠한 형태의 실행도 불가능하다.)
위 사진은 notepad.exe 파일의 시작 부분이며, PE 파일의 헤더(PE header) 부분이다.
바로 이 PE 헤더에 notepad.exe 파일이 실행되기 위해 필요한 모든 정보가 구조체 형식으로 저장되어 있다.
즉 PE File Format을 공부한다는 것은 PE 헤더 구조체를 공부한다는 것과 같은 말이다.
PE 파일의 기본 구조
DOS header 부터 Section Header 까지를 PE 헤더, 그 밑의 Section들을 합쳐서 PE 바디(Body)라고 한다.
파일에서는 offset으로, 메모리에서는 VA로 위치를 표현한다.
파일이 메모리에 로딩되면 모양이 달라진다(Section의 크기, 위치 등)
파일의 내용은 보통 코드(.text), 데이터(.data), 리소스(.rsrc)섹션에 나뉘어서 저장된다.
섹션 헤더에 각 Section에 대한 파일/메모리에서의 크기, 위치, 속성 등의 정의되어 있다.
PE 헤더의 끝부분과 각 섹션의 끝에는 NULL padding이라고 불리우는 영역이 존재한다.
VA & RVA
VA는 프로세스 가상 메모리의 절대주소를 말한다.
RVA는 어느 기준 위치에서부터의 상대주소를 말한다.
RVA + lmageBase = VA
PE 헤더 내의 정보는 RVA 형태로 된 것이 많다. (기준위치에 대한 상대주소로 저장해놓으면 재배치가 용이하기 때문)
PE 헤더
DOS Header
Microsoft는 PE File Format을 만들 때 당시에 널리 사용되던 DOS 파일에 대한 하위 호환성을 고려해서 만들었다.
그 결과로 PE 헤더의 제일 앞부분에는 기존 DOS EXE Header를 확장시킨 IMAGE_DOS_HEADER 구조체가 존재한다.
notepad.exe를 hex editor로 열어서 IMAGE_DOS_HEADER 구조체를 확인해 본 결과이다.
PE 스펙에 맞게 파일 시작 2바이트는 4D5A이며, e_lfanew 값은 000000F8이다. (리틀 엔디언 표기법)
(Intel 계열 CPU는 자료를 역순으로 저장하는데 이를 리틀 엔디언(Little Endian) 표기법이라고 한다.
DOS Stub
DOS Hedaer 밑에는 DOS Stub이 존재한다. DOS Stub의 존재 여부는 옵션이며 크기도 일정하지 않다.(DOS Stub이 없어도 파일 실행에는 문제가 없다.)
또한 DOS Stub은 코드와 데이터의 혼합으로 이루어져 있다.
notepad.exe의 DOS Stub이다.
위 사진에서 파일 offset 40~4D 영역은 16비트 어셈블리 명령어이다. 32비트 Windows OS에서는 이쪽 명령어가 실행되지 않는다.
DOS Stub은 옵션이기 때문에 개발 도구에서 지원해줘야 한다.
NT Header
NT hedaer 구조체 IMAGE_NT_HEADERS 이다.
IMAGE_NT_HEADERS 구조체는 3개의 멤버로 되어 있는데, 제일 첫 멤버는 Signature로 50450000h("PE"00) 값을 가진다. 그리고 FileHeader와 Optional Header 구조체 멤버가 있다.
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
- 출처 : Microsoft platform SDK - winnt.h -
notepad.exe의 IMAGE_NT_HEADERS의 내용을 hex editor로 살펴보자.
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_NT_HEADER, *PIMAGE_NT_HEADER;
- 출처 : Microsoft platform SDK - winnt.h -
WORD 는 4바이트 DWORD 는 3바이트로 총 25바이트의 크기를 가진 걸 알 수 있다.
이 구조체에서 위의 볼드처리 해놓은 4가지 멤버가 가장 중요하다.
# Machine #
Machine 넘버는 CPU별로 고유한 값이며 32비트 Intel x86 호환 칩은 14C의 값을 가진다.
아래는 winnt.h 파일에 정의된 Machine 넘버의 값들이다.
- 출처 : Microsoft platform SDK - winnt.h -
(내 메모장 기준으론 머신넘버가 B850이 나오는데 왜 위 자료에 없는지 모르겠다;; 내가 계산을 잘못하는 건가....)
# NumberOfSections #
PE 파일은 코드, 데이터, 리소스 등이 각각의 섹션에 나뉘어서 저장되는데
NumberOfSections는 바로 그 섹션의 개수를 나타낸다. 그런데 이 값은 반드시 0보다 커야 한다. 또 정의된 섹션 개수와 실제 섹션이 다르면 실행 에러가 발생한다.
# SizeOfOptionalHeader #
Image_NT_HEADERS 구조체의 마지막 멤버는 IMAGE_OPTIONAL_HEADER32 구조체이다.
SizeOfOptionalHeader 멤버는 바로 이 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 나타낸다. IMAGE_OPTIONAL_HEADER32는 C언어의 구조체이기 때문에 이미 그 크기가 결정되어 있다.
그런데 Windows의 PE 로더는 IMAGE_FILE_HEADER의 SizeOfOPtionalHeader 값을 보고 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 인식한다.
PE32+ 형태의 파일인 경우에는 IMAGE_OPTIONAL_HEADER32 구조체 대신 IMAGE_OPTIONAL_HEADER64 구조체를 사용한다. 두 구조체의 크기는 다르기 때문에 SizeOfOptionalHeader 멤버에 구조체 크기를 명시하는 것이다.
(IMAGE_DOS_HEADER의 e_lfanew 멤버와 IMAGE_FILE_HEADER의 SizeOfOptionalHeader 멤버 때문에 일반적인(상식적인) PE 파일 형식을 벗어나는 일명 '꽈배기' PE 파일(PE Patch)를 만들 수 있다.
# Characteristics #
파일의 속성을 나타내는 값으로, 실행이 가능한 형태인지(executable or not) 혹은 DLL 파일인지 등의 정보들이 bit OR
형식으로 조합된다.
다음은 winnt.h 파일에 정의된 Characteristics 값이다. (0002h와 2000h의 값 기억)
- 출처 : Microsoft platform SDK - winnt.h -
# IMAGE_FILE_HEADER #
Hex Editor에서 notepad.exe의 IMAGE_FILE_HEADER 구조체를 확인해 본다.
차례대로 위의
B850 은 machine 에 해당되고
9B3A 는 number of sections
00000000은 time date stamp
00000000은 offset to symbol table
00000000은 number of symbols
0000은 size of optional header
0000은 characteristics 에 해당된다.
NT Header - Optional Header
PE 헤더 구조체 중에서 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32이다.
주목해야 할 멤버들은
Magic, AddressOfEntryPoint, ImageBase, SectionAlignment, FileAlignment, SizeOfImage, SizeOfHeader, Subsystem, NumberOfRvaAndSizes, DataDirectory 가 있다.
#1 Magic
Magic 넘버는 IMAGE_OPTIONAL_HEADER32 구조체인 경우 10B, IMAGE_OPRIONAL_HEADER64 구조체인 경우 20B 값을 가진다.
#2 AddressOfEntryPoint
AddressOfEntryPoint는 EP(Entry Point)의 RVA(Relative Virtual Address) 값을 가지고 있다.
포로그램에서 최초로 실행되는 코드의 시작 주소로, 매우 중요한 값이다.
#3 ImageBase
프로세스의 가상 메모리는 0~FFFFFFFF 범위이다.(32비트의 경우). ImageBase는 이렇게 광활한 메모리에서 PE 파일이 로딩되는 시작 주소를 나타낸다.
EXE, DLL 파일은 user memory 영역인 0~7FFFFFFF 범위에 로딩되고, SYS 파일은 Kernel memory 영역인 80000000~FFFFFFFF 범위에 로딩된다. PE 로더는 PE 파일을 실행시키기 위해 프로세스를 생성하고 파일을 메모리에 로딩한 후 EIP 레지스터 값을 ImageBase + AddressOfEntryPoint 값으로 세팅한다.
#4 SectionAlignment, FileAlignment
PE 파일의 Body 부분은 섹션으로 나뉘어져 있다. 파일에서 섹션의 최소단위를 나타내는 것이 FileAlignment이고 메모리에서 섹션의 최소단위를 나타내는 것이 SectionAlignment 이다. (하나의 파일에서 FileAlignment와 SectionAlignment의 값은 같을 수도 있고 다를 수도 있다.) 파일/메모리의 섹션 크기는 반드시 각각 FileAlignment/SectionAlignment의 배수가 되어야 한다.
#5 SizeOfImage
SizeOfImage는 PE 파일이 메모리에 로딩되었을 때 가상 메모리에서 PE Image가 차지하는 크기를 나타낸다.
일반적으로 파일의 크기와 메모리에 로딩된 크기는 다르다.
#6 SizeOfHeader
SizeOfHeader는 PE 헤더의 전체 크기를 나타낸다. 이 값 역시 FileAlignment의 배수여야 한다.
파일 시작에서 SizeOfHeader 옵셋만큼 떨어진 위치에 첫 번째 섹션이 위치한다.
#7 Subsystem
이 Subsystem의 값을 보고 시스템 드라이버 파일(*.sys)인지, 일반 실행 파일(*.exe, *dll)인지 구분할 수 있다.
#8 NumberOfRvaAndSizes
NumberOfRvaAndSizes는 IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타낸다. 구조체 정의에 분명히 배열 개수가 16이라고 명시되어 있지만, PE 로더는 NumberOfRvaAndSizes의 값을 보고 배열의 크기를 인식한다. 즉 16이 아닐수도 있다는 것이다.
#9 DataDirectory
DataDirectory는 IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목마다 정의된 값을 가진다.
'컴퓨터와 보안 > 리버싱' 카테고리의 다른 글
PE File Format4 (0) | 2021.04.01 |
---|---|
PE File Format3 (0) | 2021.04.01 |
Hello World! 리버싱3 (0) | 2021.03.28 |
Hello world! 리버싱2 (0) | 2021.03.28 |
Hello World! 리버싱 (0) | 2021.03.28 |