본문 바로가기

Programming

Windows PE Structure

PE 파일 구조에서 가장 중요한 부분들을 순서대로 나열하자면


1. NtOptionalHeader 의 Data Directory

2. Import Descriptor Table, Import Address Table

3. Section Header Table

4. Resource section


정도라고 할수 있을것 같다. 그 외에도 많은 것들이 있지만 잡다한 부분이고, 여기서는 핵심적인 구조만을 기술한다.



1. NtOptionalHeader 의 Data Directory


DataDirectory 에서 IMAGE_IMPORT_DESCRIPTOR 항목이 올바르게 존재하지 않으면 로더가 EXE 실행자체를 시키지 못한다.

IMAGE_IMPORT_DESCRIPTOR 에는 중요한 2개의 항목이 기입되어야 하는데, 첫째는 Import Descriptor Table 의 RVA 주소와

그 사이즈이다.  RVA 는 ImageBase 에 대한 offset 이므로 파일 오프셋 + 테이블을 포함하는 섹션의 Base RVA로 계산된다.

예를들어 Import Descriptor Table 이 파일상에 0x1234 의 위치에 존재하고, Import Descriptor Table 을

포함하는 섹션의 Virtual Address가 0x5000 이라면 RVA 는 0x6234 가 된다.


DataDirectory 의 항목에서 일반적인 윈도우즈 Application 의 경우 아래와 같이 3개의 항목을 채워줘야 한다.

(사용하지 않는 항목은 모두 0 으로 채우면 된다)

IMAGE_DIRECTORY_ENTRY_RESOURCE 의 경우 콘솔프로그램의 경우 해당사항이 없지만 IMPORT 와 IAT 는 필수이다.

참고로 리소스 섹션의 경우 구조적으로 파싱이 되기때문에 사이즈를 지정하지 않아도 상관없다.



2. Import Descriptor Table, Import Address Table


아래는 Import Descriptor Table 의 구조를 대략적으로 설명한 그림이다.

Import Descriptor Table 은 5개의 멤버로 구성된 구조체 배열로 이루어져 있으며 각각의 구조체 Entry 는 메모리상에 로딩할

하나의 DLL 항목에 대응된다.  5개의 멤버중 중요한것은 Imported DLL Name, First Thunk 두 항목이다. 

Imported DLL Name 은 이름그대로 DLL 이름에 대한 RVA 를 가지고 있고, First Thunk 는 실제 로더가 사용하는 

IAT 에 대한 RVA 를 가지고 있다.  가장 첫 멤버는 Characteristic 이거나 Original First Thunk 에 대한 RVA 를 나타내는데, 이는 First Thunk 와 동일한 자료구조를 가리키지만 초기 데이터가 변하지 않는 일종의 Dummy 라고 보면 된다.  


Import Descriptor Table 의 구조체를 조금더 자세히 보면 다음과 같다.



Thunk 멤버들이 포인터를 가지고 있을때는 RVA 로서 바로 문자열을 참조하지만 Ordinal 형태로 값이 들어있을때는 어떻게 실제 문자열의 위치를 참조하는지 잘 모르겠다.


First Thunk, Original Firtst Thunk 는 아래와 같이 처음에는 동일한 내용으로 이루어진 배열로 구성되지만

윈도우즈 Loader 가 First Thunk 를 IAT 로서 함수 주소들로 채우게 되고 Original First Thunk 는 계속해서 유지된다.


실제로 에디터 상에서 OriginalFirstThunk 의 패턴을 검색해보면 아래와 같이 2개가 나타난다



참고할점은 이러한 IAT 항목들은 Import 할 DLL 마다 따로 존재한다는 점이다. 즉 Import Descriptor Table 은 PE 구조내에 전역적으로 1개가 존재하고 Import Address Table 은 Import Descriptor Table 의 각 항목마다 하나씩 여러개 존재한다. 그러나 IAT 가 여기저기 흩어져 있는게 아니라 모두 연속적으로 붙어있기 때문에 결국 IAT 도 전역적으로 하나의 시작지점이 있게되고 이부분을 DataDirectory 의 IAT 엔트리에 RVA 로서 기입해 줘야 한다.  또한 이들은 모두 같은 섹션내에 존재하고 RVA 로 어드레싱된다.  RVA 어드레싱의 예로 Import DLL Name 이 RVA 로 어드레싱 되는 과정은 아래와 같다.





3. Section Header Table


섹션 헤더 테이블은 다음과 같은 구조와 멤버들을 가진다



먼저 PE 헤더상에서 섹션헤더가 위치하는 오프셋이 지정되어 있고, 해당 위치부터 IMAGE_SECTION_HEADER 구조체의

배열이 나열된다.  하나의 IMAGE_SECTION_HEADER 구조체는 위의 그림과같이 여러 멤버를 가지는데, 여기서 중요한것은 Virtual Address 와 VirtualSize, Pointer To RawData 정도이다.  Virtual Address 에는 메모리상에 올라갈 섹션의 RVA 를, Virtual Size 는 섹션의 사이즈를, Pointer To RawData 는 섹션이 존재하는 파일상에서의 오프셋을 지정해주면 된다.




4. Resource section


리소스 섹션은 다음 그림과 같이 복잡한 디렉토리 구조를 가진다



IMAGE_RESOURCE_DIRECTORY 라는 루트 디렉토리가 존재하고, 여기서부터 재귀적으로 동일한 디렉토리에 대한 RVA 들을 통해

트리구조로 연결된다.  자세한 구조는 아래와같이 이루어진다.

typedef struct _IMAGE_RESOURCE_DIRECTORY {

    ULONG   Characteristics;

    ULONG   TimeDateStamp;

    USHORT  MajorVersion;

    USHORT  MinorVersion;

    USHORT  NumberOfNamedEntries;

    USHORT  NumberOfIdEntries;

} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;


IMAGE_RESOURCE_DIRECTORY 라는 구조체의 배열들이 계층적인 트리구조를 이루게 되는데, 구조체의 멤버중에 다음 구조체를

가리키는 포인터 멤버가 없다.  대신 NumverOfNamedEntries 라는 멤버가 존재하고 여기 있는 수만큼 메모리상에서 IMAGE_RESOURCE_DIRECTORY 구조체 바로 뒤에 IMAGE_RESOURCE_DIRECTORY_ENTRY 구조체가 배열로서 붙어있게 된다.

이 각각의 노드들이 리소스에 대한 루트 디렉토리 엔트리가 되고 트리상의 자식 노드에 대한 포인터는  IMAGE_RESOURCE_DIRECTORY_ENTRY 구조체의 멤버가 가지고 있다.


typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {

    ULONG   Name;

    ULONG   OffsetToData;

} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;


ENTRY 구조체의 두 필드는 상황에 따라 여러 의미로 해석된다.  Name 필드의 경우 리소스의 타입이 되거나, 리소스 이름이 되거나, 리소스 ID 가 될수 있다.  OffsetToData 멤버의 경우 rsrc 섹션을 기준으로 한 RVA 로서 다음번 DIRECTORY 를 가리킨다.

이런 멤버들이 여러 의미로 해석되는 기준은 보통 사용하지 않는 여분 bit 가 set 되어있는지 아닌지 등을 통해 결정된다.  자세한것은 구글링하면 목록이 나온다.  OffsetToData 의 경우 재귀적으로 IMAGE_RESOURCE_DIRECTORY 를 가리키거나 또는 IMAGE_RESOURCE_DATA_ENTRY 를 가리키게 된다.  IMAGE_RESOURCE_DATA_ENTRY 를 가리키는 경우 이 구조체 멤버속에

최종적인 실제 Resource 에 대한 포인터가 존재한다.(OffsetToData / Size)


typedef struct _IMAGE_RESOURCE_DATA_ENTRY {

    ULONG   OffsetToData;

    ULONG   Size;

    ULONG   CodePage;

    ULONG   Reserved;

} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;


이렇게 해서 루트 디렉토리부터 특정한 리소스까지를 찾아올 수 있다.  그러나 그 뒤의 리소스 포맷은

모든 리소스마다 다르다.



'Programming' 카테고리의 다른 글

Linux kernel memory allocation  (0) 2013.09.25
QEMU NAT configuration  (0) 2013.09.17
Linux vmarea structure  (0) 2013.09.11
IDA FLIRT Symbol Recovery  (0) 2013.09.10
ARM Soft-MMU implementation  (0) 2013.08.29