Chapter 13 PE File Format
이번 챕터의 내용은 PE File Format에 대한 내용이다. 내용 요약에 앞서, PE File Format 분석에 잘 쓰이는 툴을 먼저 소개하겠다. 챕터에 대한 요약 중 사용 될 툴이므로 다운 받아 사용하면 된다.
PEView
PE 파일 포맷은 여러 섹션으로 나뉘어져 있고 또 이 섹션은 여러 구조체, 데이터로 나뉘어 있다. PEView는 이러한 섹션과 구조체를 보기 쉽게 정리하여 보여준다.
HexEditor
실행 파일을 원하는 진수의 값으로 보여준다. 주로 16진수를 이용하며 실행시 파일의 내부 값을 변경할 수 있으므로 patching을 할때 자주 쓰인다.
1. PE 파일이란?
PE(Portable Executable) 파일은 Windows 운영체제에서 사용되는 실행 파일 형식이다. PE 파일은 32비트 형태의 실행 파일을 의미하며 PE32라는 용어를 사용하기도 한다. 64비트 형태의 실행 파일은 PE+ 또는 PE32+라고 부르며 PE 파일의 확장 형태이다.
2. PE File Format
PE 파일의 종류는 다음과 같다.
2.1 기본 구조
PE 파일의 기본 구조에 대해 알아보겠다. notepad.exe 파일은 pe 파일의 기본 구조이므로 이 파일을 예시로 한다.
그림 13.2를 보면 PE 파일의 기본 구조를 알 수 있다. DOS header부터 Section header까지를 PE 헤더, 그 밑의 Section들을 합쳐서 PE 바디라고 한다. 파일에서는 Offset으로, 메모리에서는 VA(Virtual Address)로 위치를 표현한다. 파일이 메모리에 로딩되면 모양이 달라진다. 파일의 내용은 보통 코드(.text), 데이터(.data), 리소스(.rsrc) 섹션에 나뉘어 저장된다.
섹션헤더에는 각 Section에 대한 파일/메모리에서의 크기, 위치, 속성 등이 정의 되어있다. PE 헤더의 끝부분과 각 섹션의 끝에는 NULL padding이라는 영역이 존재한다. 파일/메모리의 최소 단위를 맞추기 위한 공간이라고 생각하면 된다.
2.2 VA & RVA
VA(Virtual Address)는 프로세스 가상 메모리의 절대주소를 말하며, RVA(Relative Virtual Address)는 어느 기준 위치(ImageBase)에서 부터의 상대주소를 말한다. VA와 RVA의 관계는 다음과 같다.
RVA + ImageBase = VA
3. PE 헤더
PE 헤더는 많은 구조체로 이루어져 있다. 지금부터 각 중요한 구조체에 대해서 살펴보겠다.
3.1 DOS Header
PE File Format은 DOS 파일에 대한 하위 호환성을 고려해서 만들어졌다. 그 결과로 PE 헤더의 가장 앞부분에는 기존 DOS EXE Header를 확장시킨 IMAGE_DOS_HEADER 구조체가 존재한다.
IMAGE_DOS_HEADER 구조체의 크기는 40이다. 이 구조체에서 중요한 멤버는 e_magic, e_lfanew이다. e_magic은 DOS signature로 모든 PE 파일은 e_magic의 값이 "MZ"이다. 또한 e_lfanew의 값이 가리키는 위치는 NT Header 구조체의 위치이다.
그림 13.3을 보면 맨 첫 2바이트의 값이 4D 5A, 즉 MZ 임을 알 수 있다. 또한 IMAGE_DOS_HEADER의 크기는 40이므로 offset 00000030의 마지막 4바이트를 보면 E0 00 00 00 이다. 이는 IMAGE_DOS_HEADER의 마지막 멤버인 e_lfanew를 의미하고 이 값은 리틀 엔디언이므로 NT Header의 위치는 000000E0 임을 알 수 있다.
3.2 NT Header
NT header 구조체인 IMAGE_NT_HEADERS는 다음과 같다.
코드 13.2를 보면 IMAGE_NT_HEADERS 구조체는 3개의 멤버로 구성 되어있다. 첫 멤버는 Signature로 헥스값 50450000을 갖는다. 이 중 앞 2바이트는 "PE"를 뜻한다. IMAGE_NT_HEADERS의 크기는 F8로 상당히 큰 구조체이다.
3.3 NT Header - File Header
IMAGE_FILE_HEADER 구조체는 파일의 개략적인 속성을 나타낸다.
코드 13.3은 IMAGE_FILE_HEADER 구조체를 나타낸다. 위 멤버 중 중요한 멤버로는 Machine, NumberOfSections, SizeOfOptionalHeader, Characteristics 가 있다. 각 멤버의 특징은 다음과 같다.
Machine : CPU별로 고유한 값을 가지며, 32비트 Intel x86 호환 칩은 값 14C 를 갖는다.
NumberOfSections : 섹션의 개수를 나타냄. 이 값은 항상 0보다 크다.
SizeOfOptionHeader : IMAGE_OPTIONAL_HEADER32 구조체의 크기를 나타냄
Characteristics : 파일의 속성이 bit OR 형식으로 조합됨. (코드 13.5 참고)
3.4 NT Header - Optional Header
다음은 PE 헤더 구조체 중 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32 이다.
IMAGE_OPTIONAL_HEADER32 구조체에서 중요한 멤버는 Magic, AddressOfEntryPoint, ImageBase, SectionAlignment/FileAlignment, SizeOfImage, SizeOfHeader, Subsystem, NumberOfRvaAndSizes, DataDirectory이다. 각 멤버의 특징은 다음과 같다.
Magic : Magic 넘버는 IMAGE_OPTIONAL_HEADER32면 10B, IMAGE_OPTIONAL_HEADER64면 20B 값을 갖는다.
AddressOfEntryPoint : AddressOfEntryPoint는 Entry Point의 RVA 값이다.
ImageBase : ImageBase는 PE 파일이 로딩되는 시작 주소를 나타낸다. 일반적으로 개발도구들이 만들어 내는 EXE 파일의 Image Base 값은 00400000이고, DLL 파일의 ImageBase 값은 10000000이다. 파일이 메모리에 로딩 된 직후의 EIP 값은 ImageBase + AddressOfEntryPoint 이다.
SectionAlignment/FileAlignment : 파일에서 섹션의 최소 단위를 나타내는 것이 FileAlignment이고 메모리에서 섹션의 최소단위를 나타내는 것이 SectionAlignment이다. 파일/메모리의 섹션 크기는 반드시 각 Alignment 값의 배수가 되어야한다.
SizeOfImage : SizeOfImage는 PE 파일이 메모리에 로딩 되었을 때 가상 메모리에서 PE Image가 차지하는 크기를 나타낸다.
SizeOfHeader : SizeOfHeader는 PE 헤더의 전체 크기를 나타낸다. 이 값 역시 FileAlignment의 배수여야 한다.
Subsystem : 이 Subsystem 값을 보고 어떤 파일인지 구분 할 수 있다. 각 값의 의미는 다음과 같다.
NumberOfRvaAndSizes : NumberOfRvaAndSizes는 IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타낸다.
DataDirectory : DataDirectory는 IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목마다 정의된 값을 갖는다. 각 항목은 다음과 같다. 코드 13.7에서의 Directory란 어떤 구조체의 배열이라고 생각하면 된다. IMPORT, EXPORT Directory는 나중에 따로 설명하겠다.
3.5 섹션 헤더
섹션 헤더는 각 섹션의 속성을 정의한 것이다.
IMAGE_SECTION_HEADER
섹션 헤더는 각 섹션별 IMAGE_SECTION_HEADER 구조체의 배열로 되어있다.
IMAGE_SECTION_HEADER에서 알아야할 중요한 멤버와 의미는 다음과 같다.
4. RVA to RAW
각 섹션에서 메모리의 주소(RVA)와 파일 오프셋을 잘 매핑 할 수 있어야한다. 이러한 매핑을 RVA to RAW라고 한다. 방법은 아래와 같다.
1. RVA가 속해 있는 섹션을 찾는다.
2. 간단한 비례식을 사용하여 파일 오프셋(RAW)을 계산한다.
IMAGE_SECTION_HEADER 구조체에 의하면 비례식은 다음과 같다.
5. IAT
IAT(Import Address Table)에는 Windows 운영체제의 핵심 개념인 process, memory, DLL 구조 등에 대한 내용이 함축 되어 있다.
5.1 IMAGE_IMPORT_DESCRIPTOR
PE 파일은 자신이 어떤 라이브러리를 임포트하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시하고 있다. IMAGE_IMPORT_DESCRIPTOR의 위치는 IMAGE_OPTIONAL _HEADER의 DataDirectory[1]의 값에 정의 되어있다.
위 사진을 보면 DataDirectory의 두번째 요소는 A048(RVA) 값을 갖고 있다. 이는 RVA A048 주소에 IMAGE_IMPORT_DESCRIPTOR이 있다는 의미이다.
위 사진에서 A048(RVA)에서 IMPORT Directory Table이 시작됨을 알 수 있다.
IMAGE_IMPORT_DESCRIPTOR 구조체의 멤버 중 중요한 멤버는 다음과 같다.
1.Import Name Table(OriginalFirstThunk)
임포트하는 함수의 정보(Ordinal, Name)가 담긴 구조체 포인터 배열이다. Ordinal은 함수의 고유 번호이다. 이 정보를 얻어야 프로세스 메모리에 로딩된 라이브러리에서 해당 함수의 시작 주소를 정확히 구할 수 있다.
2.Name
라이브러리의 이름을 의미한다. 주소를 따라가보면 *.dll이 있음을 알 수 있다.
3.Import Address Table(FirstThunk)
값에 적혀있는 주소(RVA)를 따라가면 임포트하는 함수들의 시작 주소들이 포인터 배열로 존재한다.
6. EAT
Windows 운영체제에서 라이브러리란 다른 프로그램에서 불러 쓸 수 있도록 관련 함수들을 모아놓은 파일(DLL/SYS)이다. Win32 API가 대표적인 라이브러리이다. EAT(Export Address Table)는 라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 쓸 수 있게 해주는 핵심 메커니즘이다. 즉 EAT를 통해서만 해당 라이브러리에서 익스포트하는 함수의 시작 주소를 정확히 구할 수 있다.
PE 파일에서 IMAGE_EXPORT_DIRECTORY 구조체의 위치는 IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress 값으로 알 수 있다. 이 값은 IMAGE_EXPORT_DIRECTORY 구조체 배열의 시작 주소이다. IMAGE_EXPORT_DIRECTORY 구조체의 중요 멤버는 다음과 같다.
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 23 (0) | 2016.01.17 |
---|---|
Reverse Engineering Chap 14~15 (0) | 2016.01.14 |
Reverse Engineering Chap 16-17 (0) | 2016.01.14 |
Reverse Engineering Chap7-12 (0) | 2016.01.10 |
Reverse Engineering Chap1-2 (0) | 2016.01.10 |