PE포맷 이란?
윈도 운영체제에서 사용되는 실행파일(exe), DLL Object코드, FON 폰트 파일 등을 위한 파일 형식입니다.
OE파일은 윈도우 로더가 실행 가능한 코드를 관리하는데 필요한 정보를 캡슐화한 데이터 구조체입니다.
PE(Portable Executable) 말 그대로 옮겨다니면서 실행 가능한 파일
MS에서 다른 운영체제와 이식성을 좋게 하게위해 만든 파일 포맷 방식
PE 파일의 종류
실행, 드라이버, 라이브러리, 오브젝트 총 4개의 계열이 있습니다.
- 실행 계열 : EXE, SCR
- 드라이버 계열 : SYS, VXD
- 라이브러리 계열 : DLL, OCX, CPL, DRV
- 오브젝트 계열 : OBJ
PE 파일의 기본 구조
메모리에 적재(loading 또는 mapping) 될 때의 모습 PE File Format
PE(Portable Excutable) 파일 - Windows 운영체제에서 사용되는 실행 파일 형식
종류
- 실행 계열 : exe, scr
- 라이브러리 계열 : dll, ocx, cpl, drv
- 드라이버 계열 : sys, vxd
- 오브젝트 파일 계열 : obj
구조
PE Header : DOS Header ~ Section Header
PE Body : PE헤더 이후 Section들
파일을 실행하기 위한 모든 정보는 구조체 형식으로 PE헤더에 저장되어 있으며,
섹션 헤더에 각 섹션에 대한 크기/위치/속성이 정의되어 있고,
파일의 내용은 코드(. text), 데이터(. data), 리소스(. rsrc) 섹션에 나누어 저장한다.
파일에서는 offset으로 메모리에서 VA로 위치 표현
VA(Virtual Address)는 프로세스 가상 메모리의 절대 주소를 말하고
RVA(Relatve Virtual Address)는 어느 기준 위치(Imagebase)에서부터의 상대 주소를 말한다.
(DLL Relocation이 일어날 경우를 대비해, 절대 주소 대신 상대 주소를 사용한다.)
RAV + ImageBase = VA
1.PE Header
1) DOS Header - DOS 파일에 대한 하위 호환성을 고려해서 만듦
IMAGE_DOS_HEADER 고조체의 크기는 40
구조체 멤버 중..
e_magic : DOS Signature ( 4 D5 A -> ASCII값 'MZ')
e_lfanew : NT 헤더의 오프셋을 표시
2) DOS Stub - DOS 환경에서 실행되는 코드를 가진 영역, 일종의 옵션임(없어도 실행에 문제가 없으므로)
코드와 데이터의 혼합으로 이루어짐
3) NT Header
IMAGE_NT_HEADERS 구조체 - 3개의 멤버로 이루어짐
Signature : 표식, 일반적으로 50450000 h("PE" 00)을 가짐
FileHeader 구조체
Optional Header 구조체
FileHaeder 구조체 (IMAGE_FILE_HEADER)
구조체 멤버 중..
Machine : CPU별로 고유한 값
NumberOfSection : 섹션의 개수
SizeOfOptionalHeader : IMAGE_OPTIONAL_HEADER32 구조체의 크기
Characteristics: 파일의 속성(실행 가능?, 시스템 파일? dll파일? 등)
Optional Header 구조체( IMAGE_OPTIONAL_HEADER32 )
구조체 멤버 중..
Magic : 32bit 형태는 10B, 64bit 형태는 20B 값을 가짐
AddressOfEntryPoint : EntryPoint의 RVA 값을 가짐
ImageBase : PE 파일이 로딩되는 시작 주소
SectionAlignment : 섹션의 최소 단위
FileAlignment : 파일의 최소 단위
SizeOfImage : PE파일이 메모리에 로딩되었을 때 가상 메모리에서 PE 이미지 크기
SizeOfHeader : PE헤더의 전체 크기
Subsystem : 파일의 종류 구분(드라이버 파일? or CUI 파일 or GUI 파일)
NumberOfRvaAndSizes : DataDirectory 배열의 개수
DataDirectory : 구조체의 배열로 배열의 각 항목마다 정의된 값을 가짐
4) Section Header - 각 섹션의 속성을 정의함
파일/메모리 에서의 시작 위치, 크기, 접근 권한 등
code 섹션 - 실행, 읽기 권한
data 섹션 - 비실행, 읽기, 쓰기 권한
resource 섹션 - 비실행, 읽기 권한
IMAGE_SECTION_HEADER 구조체
구조체 멤버 중..
VirtualSize : 메모리에서 섹션 크기
VirtualAddress : 메모리에서 섹션의 시작 주소(RVA)
SizeOfRawData : 파일에서 섹션 크기
PointerToRawData : 파일에서 섹션 시작 위치
Characteristics : 섹션의 속성
Name : 섹션 이름
RVA to RAW 계산
PE 파일이 메모리에 로딩되었을 때 각 섹션에서 메모리의 주소(RVA)와 파일 옵셋을 매핑하는 방법
1) RVA가 속한 섹션을 찾는다.
2) 간단한 비례식을 통해 파일 옵셋(RAW) 계산
RAW-PointerToRawData = RVA - VirtualAddress 이므로
RAW = RVA - VirtualAddress + PointerToRawData 가 됩니다.
파일의 오프셋 = 메모리 상대 주소 - 메모리의 해당 섹션 시작 위치 + 파일의 해당 섹션 시작 위치
DLL
메모리 낭비를 피하기 위하여,
프로그램에 라이브러리를 포함시키지 말고, 별도의 파일(DLL)을 구성하여 필요할 때마다 불러 쓴다.
한 번 로딩된 DDLL의 코드, 리소스는 Memory Mapping 기술로 여러 프로세스에서 공유해 사용
로딩 방식
- Explicit Linking : 프로그램에서 사용되는 순간에 로딩, 사용 후 해제
- Implicit Linking : 프로그램 시작 시 로딩, 종료 시 해제
IAT ( Import Address Table )
프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지 나타낸 테이블
DLL의 Implicit Linking 방식에 대한 메커니즘을 제공한다.
1) IMAGE_IMPORT_DESCRIPTOR (IID)
구조체 배열 중..
OriginalFirstThunk : INT(Import Name Table)의 주소(RVA)
Name : 라이브러리 이름 문자열의 주소(RVA)
FirstThunk : IAT의 주소(RVA)
INT와 IAT는 long 타입의 4바이트 자료형 배열
INT의 각 원소 값은 IMAGE_IMPORT_BY_NAME 구조체 포인터
2) IAT 입력 순서
1. IID의 Name 멤버를 읽어 라이브러리 이름 문자열을 찾는다.
2. 해당 라이브러리 로딩 -> LoadLibrary(“이름”)
3. IID의 OriginalFirstThunk 멤버를 읽어 INT의 주소를 찾는다.
4. INT의 배열에서 하나씩 값을 읽어, IMAGE_IMPORT_BY_NAME의 주소를 찾고
5. IMAGE_IMPORT_BY_NAME의 ordinal, Name 항목을 통해 해당 함수의 주소를 찾는다.
6. IID의 FirstThunk를 통해 IAT 주소를 찾고
7. 해당 IAT 배열에 찾은 함수의 주소 입력
8. 4~7 과정 반복
Ordinal 이란, 함수의 고유 번호를 나타내는 2바이트 값
EAT( Export Address Table )
라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 하는 메커니즘
1) IMAGE_EXPORT_DESCRIPTOR
구조체 배열 중..
NumberOfFunctions : 실제 Export 함수 개수
NumberOfNames : Export 함수 중 이름을 가진 함수 개수
AddressOfFunctions Export : 함수 주소 배열
AddressOfNames : 함수 이름 주소 배열
AddressOfNameOrdinals : Ordinal 주소 배열
2) GetProcAddress()
라이브러리에서 함수 주소를 얻는 API
동작 원리
1. AddressOfNames 멤버를 이용해 ‘함수 이름 배열’로 감
2. ‘함수 이름 배열’에 저장된 문자열 주소들을 문자열 비교하여 원하는 함수를 찾는다. -> index
3. AddressOfNameOrdinals 멤버를 이용해 ‘Ordinal 배열’로 감
4. ‘Ordinal 배열’에서 index를 이용해 해당 ordinal 값을 찾음
5. AddressOfFunctions 멤버를 이용해 ‘함수 주소 배열’ (EAT)로 감
6. EAT에서 아까 구한 ordinal을 배열 인덱스로 원하는 함수의 시작 주소를 얻는다.
이름 없이 ordinal만으로 함수의 주소를 찾을 땐
(Ordinal - IMAGE_EXPORT_DIRECTOR.BASE 멤버 )를 EAT의 인덱스로 함