0%

PE 구조 이해하기 - IMAGE_DATA_DIRECTORY 구조

windows 시스템 실행파일의 구조와 원리 책 내용을 요약 및 정리 하는 포스팅이에요.

주요 섹션들과 정보들의 위치와 크기를 나타내는 IMAGE_DATA_DIRECTORAY에 대한 내용을 알아볼 거에요.


IMAGE_DATA_DIRECTORY의 시작

이 구조체에는 주요 섹션들과 정보들의 위치와 크기를 나타내는 값들이 저장되어 있어요.
구조체 배열의 크기에 대한 값이 매크로 상수 값(IMAGE_UNMBEROF_DIRECTROY_ENTRIES)으로 지정이 되어 있지만, 공식 문서에서는 항상 16개로 고정되어 있지 않다고해요.
따라서 IMAGE_DATA_DIRECTORY 구조체의 배열의 개수를 알기 위해서는 IMAGE_OPTIONAL_HEADER 구조체의 NumberOfRvaAndSizes 멤버를 확인해야 되요.


지금은 16개로 고정되있다고 생각을 할게요!
16개가 있다면 이 구조체는 128바이트로 이루어진 구조체 배열이에요.
그리고 이 배열의 마지막 엔트리, 인덱스 15에 해당하는 엔트리는 언제나 0으로 세팅되야 한다고 해요.

즉, 배열의 인덱스 0부터 NumberOfRvaSizes-1까지의 인덱스가 지정하는 필드가 모두 사용되는 것이 아니라 마지막에 해당하는 인덱스를 제외하고 사용하는 거죠.

각 엔트리들은 나름대로의 의미를 가지고 있고요. 특히 병합된 섹션과 관련된 정보가 엔트리에 들어가기 때문이에요. 그래서 이 필드는 반드시 참조해야 하는 영역입니다.

각 배열의 엔트리의 의미는 이것 또한 “WinNT.h” 헤더 파일에 정의되어 있어요.

1
2
3
4
5
6
7
#define IMAGE_UNMBEROF_DIRECTORAY_ENTRIES 16

typedef struct _IMAGE_DATA_DIRECTORY
{
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTROY, *PIMAGE_DATA_DIRECTROY:

VirtualAddress 와 Size는 각각 해당 IMAGE_DATA_DIRECTORY_ENTRY의 인덱스에 해당하는 미리 지정된 섹션 또는 블록의 정보에 대한 시작 주소와 그 크기를 가리키는 RVA 이에요.

예를 들면, 인덱스 0번은 Export Directory(내보내기 함수 테이블)에 대한 RVA와 크기이고, Import Directory(가져오기 함수 테이블)에 대한 RVA와 크기에요.


IMAGE_DATA_DIRECTORY_ENTRY의 종류

이것 또한 각 인덱스의 의미는 “WinNT.h” 헤더 파일에 정의되어 있죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Directory Entries

#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

0번 인덱스, IMAGE_DIRECTORY_ENTRY_EXPORT

익스포트 테이블(Export Directory Table), IMAGE_EXPORT_DIRECTORY 구조체의 시작 번지를 가리켜요. 이 시작 번지가 익스포트 섹션의 시작 주소에요.


1번 인덱스, IMAGE_DIRECTORY_ENTRY_IMPORT

임포트 테이블(Import Directory Table), IMAGE_IMPORT_DESCRIPTOR 구조체 배열의 번지를 가리켜요. 이 시작 번지가 임포트 섹션의 시작 주소에요.


2번 인덱스, IMAGE_DIRECTORY_ENTRY_RESOURCE

리소스, IMAGE_RESOURCE_DIRECTORY 구조체의 시작 번지를 가리켜요. 이 시작 번지가 리소스 섹션의 시작 주소에요.


3번 인덱스, IMAGE_DIRECTORY_ENTRY_EXCEPTION

예외 핸들러 테이블(Exception Directory Table), IMAGE_RUNTIME_FUNCTION_ENTRY 구조체 배열의 시작 번지를 가리켜요. CPU에 의존적이고, 테이블 기반 예외 핸들링을 위한 것이죠. x86 계열을 제외한 모든 CPU상에서 사용되는데, X86 기반의 윈도우 PE 파일에서는 의미가 없는 영역이에요.

IA-64 CPU, 64비트 CPU에서는 사용하는 영역인거죠!!


4번 인덱스, IMAGE_DIRECTORY_ENTRY_SECURITY

“WinTrust.h” 헤더 파일에 정의되어 있는 WIN_CRETIFICATEW 구조체들의 리스트 시작 번지를 가리켜요.
이 리스트는 메모리 상에 매핑되지 않기 때문에 VirtualAddress 필드 값은 RVA가 아니라 파일 오프셋에 해당되요.


5번 인덱스, IMAGE_DIRECTORY_ENTRY_BASERELOC

기본 재배치(Base Relocation) 정보를 가리켜요.

재배치는 무엇일까요?
로더가 실행 모듈을 원하는 위치, 즉 IMAGE_OPTIONAL_HEADER의 ImageBase 필드에 지정된 가상 주소 공간의 주소에 위치시키지 못했을 때 코드 상의 포인터 연산과 관련된 주소를 다시 갱신해야 하는 경우를 재배치 라고 불러요!

저번에 말했듯이 EXE의 경우에는 이런 상황이 거의 없어요. 그 이유는 EXE 파일보다 DLL이 먼저 메모리에 로딩되기 때문이에요. 그에 반면 DLL의 경우에는 빈번히 발생하죠.
이를 위해 재배치 섹셔이 존재하는데, 이 필드가 재배치 섹션의 시작 주소를 가리켜요.


6번 인덱스, IMAGE_DIRECTORY_ENTRY_DEBUG

IMAGE_DEBUG_DIRECTORY 구조체의 배열을 가리키는 번지에요.
각각 해당 이미지의 디버그 정보를 가지고 있죠.

초기 볼랜드 링커의 경우 이 인덱스에 해당하는 IMAGE_DATA_DIRECTORY 엔트리의 Size 필드를 이 섹션의 바이트 수가 아니라 구조체의 수로 세트했다고 해요.

물론 지금은 섹션의 크기를 가지고 있어요!
따라서 IMAGE_DEBUG_DIRECTORY 구조체의 수를 얻기 위해서는 Size 필드의 값을 IMAGE_DEBUG_DIRECTORY 구조체의 바이트 수(28 바이트)로 나누면 되요.


7번 인덱스, IMAGE_DIRECTORY_ENTRY_ARCHITECTURE

아키텍처에 대한 구체적인 데이터, IMAGE_ARCHITECTURE_HEADER 구조체의 배열에 대한 시작 번지를 가리켜요.

X86 또는 IA-64 계열에서는 사용하지 않아요.
애초에 인텔 계열 CPU를 위해 만들어진 운영체제 이기 때문에 아키텍처에 대한 구체적인 데이터가 필요가 없는거죠!


8번 인덱스, IMAGE_DIRECTORY_ENTRY_GLOBALPTR

글로벌 포인터 레지스터(GP, Global Pointer)로 사용되는 시작 번지를 가리켜요.
x86에서는 사용되지 않지만, IA-64 에서는 사용되요.
여기서 Size 필드는 필요가 없어요. 사용되지 않아요.


9번 인덱스, IMAGE_DIRECTORY_ENTRY_TLS

스레드 지역 저장소 초기화 섹션에 대한 시작 번지를 가리켜요.
별도의 TLS 함수를 사용하지 않고 __declspec(thread)라는 지시어를 통해 변수가 선언되면 이 변수는 TLS에 들어가게 되고 이를 위해 링커는 별도의 TLS 섹션을 만들게 되죠.


10번 인덱스, IMAGE_DIRECTROY_ENTRY_LOAD_CONFIG

IMAGE_LOAD_CONFIG_DIRECTORY 구조체애 대한 시작 번지를 가리켜요.
Windows NT, Windows 2000, Windows XP의 구체적인 정보가 들어가 있다고해요.

이 구조체를 실행 파일에 집어 넣으러면 IMAGE_LOAD_CONFIG_DIRECTORY 구조체를 _load_config_used라는 이름으로 전역적으로 정의 해야 한다고 해요

1
2
3
extern "C"

IMAGE_LOAD_CONFIG_DIRECTORY __load_config_used = {...};

11번 인덱스, IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT

IMAGE_BOUND_IMPORT_DESCRIPTOR 구조체 배열에 대한 시작 번지를 가리켜요.
DLL 바인딩과 관련된 정보를 가리켜요.

이 필드도 중요한 녀석이죠! 나중에 더 상세하게 다룰 예정입니다!


12번 인덱스, IMAGE_DIRECTORY_ENTRY_IAT

첫 번째 임포트 주소 테이블(IAT, Import Address Table)의 시작 번지를 가리켜요.
임포트된 각각의 DLL에 대한 IAT는 메모리 상에서 연속적으로 나타나게 되요.
Size 필드는 모든 IAT의 전체 크기를 가리켜요.

로더는 임포트 섹션을 해석할 동안 이 엔트리의 주소와 크기를 이용해서 임시적으로 IAT들을 읽기/쓰기 모드로 마킹한다고 해요.


13번 인덱스, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT

Visual C++ 이 제공하는 DELAYTMP.H 헤더 파일에 정의되어 있는 ImgDelayDescr 구조체의 배열을 가리키는 시작 번지에요.

지연 로딩 DLL은 해당 API가 처음으로 호출되기 전까지 로드되지 않아요.
윈도우는 지연 로딩 DLL에 대한 어떠한 암시적인 정보고 가지고 있지 않아요.
전적으로 지연 로딩의 정보는 링커나 런타임 라이브러리에 구현되어 있죠.


14번 인덱스, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR

업데이트된 시스템 헤더 파일에 IMAGE_DIRECTORY_ENTRY_COMHEADER 라는 이름으로 변경되었다고 해요.

이 필드의 정보는 .NET 응용 애플리케이션이나 DLL용 PE를 위한 거라고 해요.
PE 내의 .NET 정보에 대한 최상위 정보의 시작 번지를 가리킨다고 해요.

이 정보 같은 경우에는 IMAGE_COR20_HEADER 구조체의 형태로 구성되었다고 합니다.


마지막으로…

모두 다 알면 좋겠지만, Export Directory, Import Directory, Exception Directory, Base Relocation Table, Bound Import Directory, Import Address Table(IAT), Delay Load Import Descriptors에 해당 하는 섹션들은 확실하게 공부를 해야한다고 생각을 합니다!






참고자료


IMAGE_DATA_DIRECTORY