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 |