공부해요/리버싱_핵심원리

13. PE File Format

yenas0 2024. 10. 2. 09:16
반응형

PE(Portable Executable) 파일은 Windows 운영 체제에서 사용되는 실행 파일 형식을 의미하며, Microsoft에서 기존 UNIX 시스템에서 사용된 COFF(Common Object File Format)를 기반으로 개발됨. 주로 32비트 및 64비트 Windows 시스템에서 실행되는 프로그램 및 라이브러리를 위해 설계됨.

  • 32비트 파일은 PE(Portable Executable) 형식을 따르며, 64비트 파일은 PE+ 또는 PE32+로 불림.

 

 

PE File Format

종류 주요 확장자
실행 계열 EXE, SCR
라이브러리 계열 DLL, OCX, CPL, DRV
드라이버 계열 SYS, VXD
오브젝트 파일 계열 OBJ

 

  • EXE 파일: 주로 프로그램 실행 파일로 사용되며, 사용자가 직접 실행 가능함.
  • DLL 파일: 동적 링크 라이브러리 파일로, 여러 프로그램에서 공통적으로 사용되는 함수와 리소스를 포함함.
  • SYS 파일: 시스템 드라이버 파일로, 하드웨어와 운영체제 간의 인터페이스를 제공함.
  • OBJ 파일: 오브젝트 파일로, 컴파일된 소스 코드가 포함되어 있으나 직접 실행은 불가능하며, 링크 과정을 거쳐야 실행 가능함.

실행 가능 여부

  • OBJ 파일을 제외한 모든 파일은 실행 가능한 파일로 간주됨.
    EXE, SCR, DLL, SYS 파일 등은 모두 실행할 수 있는 형식이나, 일부는 쉘에서 직접 실행되지 않음. 예를 들어, DLL 및 SYS 파일은 디버거, 서비스 또는 기타 실행 방법을 통해 실행 가능함

 

위는 notepad.exe 파일의 시작 부분, PE 파일의 헤더 부분.

이 PE 헤더에 notepad.exe 파일이 실행되기 위해 필요한 모든 정보

 

 

 

 

 

기본 구조

notepad.exe는 일반적인 PE 파일의 기본 구조

notepad.exe 파일이 메모리에 적재될 때의 모습

 

PE 파일의 구성 요소

PE 파일은 DOS Header, PE Header, Section Header, 그리고 실제로 코드와 데이터를 담고 있는 **섹션(Section)**들로 구성됨. 이 전체 구조를 PE Body라고 부르며, 파일이 메모리에 어떻게 로드되고 실행될지를 결정하는 중요한 정보를 포함하고 있음.

  1. DOS Header
    • DOS 시절의 호환성을 위해 포함된 부분으로, 주로 "이 프로그램은 DOS 모드에서 실행될 수 없습니다"라는 메시지를 포함하고 있음.
    • e_lfanew 필드에 PE 헤더의 시작 위치를 기록함.
  2. PE Header
    • PE 파일의 핵심적인 정보를 포함하는 부분으로, CPU 아키텍처, 섹션 수, 이미지 크기, 메모리 매핑 정보 등 프로그램 실행에 필요한 메타데이터를 제공함.
    • PE 헤더의 중요한 필드:
      • Machine: 실행되는 CPU 아키텍처를 나타냄 (예: 0x14C는 x86).
      • NumberOfSections: 파일에 포함된 섹션의 수.
      • TimeDateStamp: 파일이 생성된 시간.
      • PointerToSymbolTable: 심볼 테이블의 오프셋(주로 디버깅 목적으로 사용됨).
      • Characteristics: 파일의 특성을 나타내며, 실행 가능 여부 등을 포함함.
  3. Section Header
    • 각 섹션(.text, .data, .rsrc 등)의 파일과 메모리에서의 위치, 크기, 속성 등을 정의하는 부분임.
    • 섹션 헤더는 각 섹션의 파일 내 위치와 메모리 내 위치, 섹션의 크기, 실행 권한 등을 정의함. 예를 들어, .text 섹션은 실행 코드, .data 섹션은 초기화된 전역 변수, .rsrc 섹션은 리소스(아이콘, 이미지 등)를 포함함.

PE Body

  • PE Body는 PE 헤더와 각 섹션을 포함하는 전체 구조를 의미함. PE 파일이 메모리에 로드될 때, 이 구조에 따라 파일이 가상 메모리의 적절한 위치에 매핑됨. PE Body는 파일의 실행 흐름과 메모리에서의 배치를 정의하는 중요한 부분임.

 

 

VA (Virtual Address) & RVA (Relative Virtual Address)

PE 파일이 메모리에 로드될 때, 파일 내 각 섹션은 메모리 상의 가상 주소(VA)에 매핑됨.

  1. VA (Virtual Address):
    VA는 가상 메모리 상의 절대 주소를 나타냄. 프로세스가 실행될 때 할당되는 가상 메모리 공간 내에서의 위치를 정의함.
  2. RVA (Relative Virtual Address):
    RVA는 기준 주소로부터의 상대적인 위치를 나타냄. PE 파일의 데이터는 대부분 RVA로 저장되며, 프로세스가 메모리에 로드될 때 이 RVA를 기반으로 VA로 변환됨. RVA를 사용하면 PE 파일이 메모리의 다양한 위치에 로드되더라도 코드 참조가 변하지 않음. 이 때문에 PE 파일은 재배치(Relocation) 과정을 쉽게 처리할 수 있음.

재배치(Relocation)와 RVA

PE 파일이 메모리 상의 특정 위치에 로드될 때, 다른 프로그램에 의해 해당 메모리 공간이 이미 사용 중일 수 있음. 이 경우 **재배치(relocation)**가 필요하며, PE 파일의 주소 정보는 다른 위치로 이동됨. 이 과정에서 RVA는 기준 주소로부터의 상대적인 위치를 참조하므로, 재배치가 발생해도 주소 참조의 정확성을 유지할 수 있음.

NULL Padding

PE 헤더의 끝부분과 각 섹션의 끝에는 NULL padding이 포함되어 있음. 이 NULL padding은 메모리 정렬을 위해 사용되며, 섹션의 경계를 맞추고 메모리 접근을 최적화하기 위한 공간

 

 

 

 

 

 

 

 

PE 파일 구조

DOS 헤더 (IMAGE_DOS_HEADER)
DOS 헤더는 PE 파일의 시작 부분에 위치하며, DOS 모드에서 실행되는 프로그램을 위한 공간을 제공함. 주로 "이 프로그램은 DOS 모드에서 실행될 수 없습니다"라는 메시지와 PE 헤더로 이동하기 위한 오프셋 정보가 포함됨. 이 헤더의 가장 중요한 필드는 e_lfanew로, 이는 PE 헤더가 시작하는 위치를 가리킴.

Dos Stub

Dos Header 밑에 존재. 옵션. 크기 일정하지 않음

 

notpad의 Dos stub

화면 문자열에 Tis program cannot be run in DOS mode출력 후 종료

32비트용 PE파일이지만 MS-DOS 호환 모드를 가지고 있어 도스 환견에서 실행 시 도시 EXE 실행 코드 동작하며 해당 문자열을 출력후 종료됨.

 

 

 

 

 

 

NT Header

PE 헤더는 파일의 중요한 메타데이터를 포함하고 있음. 여기에는 파일이 실행될 CPU 아키텍처, 이미지 크기, 타임스탬프 및 프로그램의 엔트리 포인트 주소 등이 포함됨.

  • 파일 헤더 (IMAGE_FILE_HEADER): 파일에 대한 기본 정보를 제공함. 여기에는 섹션의 수, 타임스탬프, 실행 파일의 특성 등이 포함됨.
  • 옵션 헤더 (IMAGE_OPTIONAL_HEADER): 실행 파일의 추가적인 정보를 제공함. 이 헤더는 프로그램의 진입 지점, 이미지의 크기, 메모리 레이아웃 등을 정의함.
typedef struct _IMAGE_NT_HEADERS {
	DWORD Siginature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADR32, *PIMAGE_NT_HEADERS32;

3개의 멤버로 구성

제일 첫번째는 Signature로 PE00값을 가짐.

IMAGE_NT_HEADER의 구조체 크기는 F8이다.

- File header

typedef struct _IMAGE_FILE_HEADER {
	WORD Machine;
    WORD NumberOFSections;
    DWORD TimeDateStamp;
    DWORD PointToSymbolTable;
    DWORD NumberOfSymbols;
    WORD SizeOfOptionalHeader;
    WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 

Machine넘버: CPU별로 고유, 32비트 Intel x86호환 칩은 14c의 값을 가짐

NumberOfSections: PE파일은 코드, 데이터, 리소스 등이 각각의 섹션에 나뉘어 저장되는데 그 섹션의 개수

SizeOfOptionalHeader: IMAGE_OPTIONAL_HEADER32구조체의 크기 나타냄

Characteristics: 파일의 속성 나타냄

 

 

- Optional Header

PE 헤더 구조체 중에서 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32

Magic넘버 IMAGE_OPTIONAL_HEADER32의 경우 10B
IMAGE_OPTIONAL_HEADER64의 경우 20B
AddressOfEntryPoint EP의 RVA값을가짐 
ImageBase 메모리에서 PE파일이 로딩되는 시작 주소
SectionAlignment, FileAlignment FileAlignment: 파일에서 섹션의 최소단위를 나타냄
SectionAlignment: 메모리에서 섹션의 최소단위를 나타냄
SizeOfImage PE파일이 메모리에 로딩되었을 때 가상 메모리에서 PE Image가 차지하는 크기 나타냄
SizeOfHeader PE헤더의 전체 크기
Subsystem 시스템 드라이버 파일인지 일반 실행 파일인지 구분할 수 있게함
NumberOfRvaAndSizes DataDirectory배열의 개수
DataDirectory IMAGE_DATA_DIRECTORY 구조체의 배열

 

 

 

섹션 헤더 (IMAGE_SECTION_HEADER)
섹션 헤더는 실행 파일이 포함하고 있는 코드, 데이터 및 리소스에 대한 정보를 담고 있음. 각 섹션은 파일의 다른 부분에 로드되며, 실행 가능 여부, 읽기 및 쓰기 권한 등을 나타냄.

 

IMAGE_SECTION_HEADER

섹션 헤더는 각 섹션별 IMAGE_SECTION_HEADER 구조체의 배열로 되어있음

항목 의미
VirtualSize 메모리에서 섹션이 차지하는 크기
VirtualAddress 메모리에서 섹션의 시작 주소(RVA)
SizeOfRawData 파일에서 섹션이 차지하는 크기
PointerToRawData 파일에서 섹션의 시작 위치
Characteristics 센션의 속성(bit OR)

 

노트패드의 섹션 헤더 배열이다.

 

 

 

 

RVA to RAW

: PE 파일이 메모리에 로딩되었을 때 각 섹션에서 메모리 주소와 파일 옵셋 매핑하는 것

방법

1. RVA가 속해 있는 섹션을 찾음

2. 간단한 비례식을 통해 RAW(파일 옵셋)을 계산

IMAGE_SECTION_HEADER 구조체에 의한 비례식

RAW - PointerToRawData = RVA - VirtualAddress
RAW = RVA - VirtualAddress + PointerToRawData

 

 

 

IAT(Import Address Table)

IAT는 Import Address Table의 약자로, PE 파일이 실행될 때 사용되는 프로세스, 메모리, DLL 구조에 대한 정보를 포함하는 테이블임. IAT는 PE 파일이 참조하는 외부 DLL 함수들의 주소를 저장하며, 프로그램이 실행될 때 운영체제는 이 테이블을 이용해 해당 DLL의 함수를 동적으로 로드함.

DLL (Dynamic Link Library)

DLL은 동적 라이브러리 연결을 위한 파일 형식으로, 프로그램이 실행될 때 필요에 따라 외부 라이브러리를 동적으로 불러와 사용하기 위해 설계됨.

  • 라이브러리의 분리: 프로그램에 필요한 라이브러리 코드를 직접 포함시키지 않고, 별도의 파일로 관리하여 필요할 때마다 로드함. 이를 통해 메모리 사용량을 줄이고, 프로그램 크기를 작게 유지할 수 있음.
  • Memory Mapping: 한번 로딩된 DLL의 코드와 리소스는 메모리 매핑(memory mapping) 기술을 통해 여러 프로세스에서 공유되어 사용됨. 동일한 DLL을 여러 프로그램이 사용할 경우, 메모리 내에 중복된 코드가 로드되는 것을 방지하여 효율적인 메모리 관리를 가능하게 함.
  • 업데이트 편리성: DLL은 독립적인 파일로 존재하므로, 프로그램을 수정할 필요 없이 라이브러리가 업데이트되었을 때 DLL 파일만 교체하면 됨. 이는 유지보수와 업데이트 과정에서 큰 이점을 제공함.

 

 

DLL 로딩 방식

DLL은 두 가지 방식으로 프로그램에서 로드될 수 있음:

  1. Explicit Linking:
    • 명시적 로딩 방식으로, 프로그램이 실행 도중 특정 DLL을 필요로 할 때 명시적으로 로드함.
    • 프로그램에서 DLL의 함수를 사용할 때 LoadLibrary와 GetProcAddress 함수를 사용하여 동적으로 로드하고, 사용이 끝나면 FreeLibrary를 호출해 메모리에서 해제함.
  2. Implicit Linking:
    • 암시적 로딩 방식으로, 프로그램 시작과 동시에 필요한 모든 DLL이 로드됨.
    • 프로그램이 종료될 때까지 DLL이 메모리에 유지되며, 프로그램 종료 시 자동으로 메모리에서 해제됨. 이 방식은 대부분의 응용 프로그램에서 흔히 사용됨.

 

 

IMAGE_IMPORT_DESCRIPTOR

IMAGE_IMPORT_DESCRIPTOR는 PE 파일 내에서 어떤 DLL을 임포트하는지 명시하는 구조체임. 이 구조체는 프로그램이 의존하는 라이브러리의 정보를 담고 있으며, 운영체제는 이를 참고해 프로그램 실행 시 해당 라이브러리를 로드함.

이 구조체는 IAT와 함께 동작하며, 프로그램이 참조하는 모든 외부 라이브러리와 함수 주소를 명확하게 정의하여 PE 파일이 로드될 때 필요한 동적 연결을 수행

https://thejn.tistory.com/95

 

항목 의미
OriginalFirstThunk INT의 주소(RVA)
Name Library 이름 문자열의 주소(RVA)
FirstThunk IAT의 주소(RVA)

 

PE로더가 임포트 함수 주소를 IAT에 입력하는 순서

1. IID의 Name 멤버를 읽어서 라이브러리의 이름 문자열 얻음
2. 해당 라이브러리 로딩
3. IID의 OriginalFirstTHunk멤버를 읽어 INT 주소 얻음
4. INT에서 배여르이 값을 하나씩 읽어 해당 IMAGE_IMPORT_BY_NAME 주소를 얻음
5. IMAGE_IMPORT_BY_NAME의 Hint또는 Name 항목을 이용하여 해당 함수의 시작 주소 얻음
6. IID의 FirstThunk 멤버를 읽어서 IAT 주소를 얻음
7. 해당 IAT 배열 값에 위에서구한 함수 주소를 입력
8. INT가 끝날 때까지 위 4~7 과정 반복 

 

 

EAT (Export Address Table)

**EAT(Export Address Table)**는 라이브러리에서 제공하는 함수들의 주소를 관리하는 테이블임. 윈도우 운영체제에서 라이브러리 파일(DLL 등)은 다른 프로그램이 특정 기능을 불러다 사용할 수 있도록 여러 함수들을 포함하고 있으며, EAT는 그 함수들의 시작 주소를 관리하는 핵심 메커니즘임.

EAT는 다음과 같은 역할을 수행함:

  • 라이브러리 함수의 시작 주소 제공: EAT는 라이브러리(DLL)가 제공하는 모든 익스포트(export) 함수의 시작 주소를 포함함. 이를 통해 다른 프로그램이 해당 라이브러리에서 제공하는 함수를 사용할 수 있게 됨.
  • 정확한 함수 주소 참조: 다른 프로그램이 라이브러리의 함수를 호출할 때, EAT를 사용해 해당 함수의 메모리 상의 정확한 주소를 참조함으로써 함수 호출이 가능함.
  • 다른 프로그램과의 인터페이스 역할: EAT는 다른 프로그램이 해당 라이브러리에서 제공하는 함수를 사용할 수 있도록 인터페이스 역할을 수행함. 프로그램은 EAT를 통해 라이브러리의 특정 함수에 접근할 수 있음.

 

 

 

IMAGE_EXPORT_DIRECTORY

IMAGE_EXPORT_DIRECTORY는 PE 파일 내에서 라이브러리의 익스포트 함수들을 정의하는 구조체임. 이 구조체는 EAT의 중요한 부분을 구성하며, PE 파일이 제공하는 모든 익스포트 함수의 정보를 담고 있음. 이를 통해 운영체제는 프로그램이 호출하는 함수의 위치를 정확히 찾아내어 해당 함수를 실행할 수 있음.

항목 의미
NumberOfFunctions 실제 Export 함수 개수
NumberOfNames Export 함수 중에서 이름을 가지는 함수 개수
AddressOfFunctions Export 함수 주소 배열
AddressOfNames 함수 이름 주소 배열
AddressOfNAmeOrdinals Ordinal 배열

IMAGE_EXPORT_DIRECTORY의 주요 필드:

  • Characteristics: 라이브러리의 특성을 나타내는 필드.
  • TimeDateStamp: 라이브러리가 생성된 시간.
  • MajorVersion / MinorVersion: 라이브러리의 버전 정보.
  • Name: 라이브러리의 이름을 나타냄.
  • Base: 익스포트 테이블에서 첫 번째 함수의 인덱스.
  • NumberOfFunctions: 익스포트된 함수들의 총 개수.
  • NumberOfNames: 익스포트된 함수 이름의 총 개수.
  • AddressOfFunctions: 함수의 주소를 저장하는 배열로, 해당 라이브러리에서 제공하는 모든 함수의 시작 주소를 가리킴. EAT와 연결됨.
  • AddressOfNames: 익스포트된 함수 이름을 가리키는 포인터 배열.
  • AddressOfNameOrdinals: 익스포트 함수의 오디널(ordinal)을 가리키는 배열로, 각 함수의 인덱스 정보가 저장됨.


EAT와 IMAGE_EXPORT_DIRECTORY의 관계

EAT는 IMAGE_EXPORT_DIRECTORY 구조체의 정보를 기반으로, PE 파일이 익스포트하는 함수들의 주소를 정확하게 제공함. 라이브러리에서 제공하는 모든 익스포트 함수는 EAT에 기록되며, 다른 프로그램이 이 테이블을 참조해 함수를 호출할 수 있음. IMAGE_EXPORT_DIRECTORY는 EAT를 통해 라이브러리 함수의 시작 주소와 이름, 오디널 정보를 제공하여 함수 호출을 가능하게 함.

 

 

 

 

라이브러리에서 함수 주소는 얻는 API = GetProcAddress()

동작원리

1. AddressOfNames 멤버를 이용해 '함수 이름 배열'로 
2. '함수 이름 배열'은 문자열 주소가 저장되어 있음. 문자열 비교를 통해 원하는 함수 이름 찾기
3. AddrssOfNameOrdinals 멤버를 이용해 'ordinal 배열'로
4. 'ordinal 배열'에서 name_index로 해당 ordinal 값을 찾음
5. AddressOfFunctions 멤버를 이용해 '함수 주소 배열(EAT)'로
6. '함수 배열 주소(EAT)'에서 아까 구한 ordinal을 배열 인덱스로하여 원하는 함수의 시작 주소 얻음 

 

 

Advanced PE

추천 프로그램

PEView.exe

Patched PE

반응형