1 /**
2  * Support for the Windows PE executable format.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.sys.windows.pe.pe;
15 
16 import std.exception;
17 import std.string;
18 
19 import ae.sys.windows.imports;
20 mixin(importWin32!q{winnt});
21 
22 struct PE
23 {
24 	ubyte[] data;
25 
26 	PIMAGE_DOS_HEADER dosHeader;
27 	PIMAGE_NT_HEADERS32 ntHeaders;
28 	IMAGE_SECTION_HEADER[] sectionHeaders;
29 	IMAGE_DATA_DIRECTORY[] dataDirectories;
30 
31 	this(void[] exe)
32 	{
33 		data = cast(ubyte[])exe;
34 
35 		enforce(data.length > IMAGE_DOS_HEADER.sizeof, "Not enough data for DOS header");
36 		dosHeader = cast(PIMAGE_DOS_HEADER)data.ptr;
37 		enforce(dosHeader.e_magic == IMAGE_DOS_SIGNATURE, "Invalid DOS signature");
38 
39 		enforce(data.length > dosHeader.e_lfanew + IMAGE_NT_HEADERS32.sizeof, "Not enough data for NT headers");
40 		ntHeaders = cast(PIMAGE_NT_HEADERS32)(data.ptr + dosHeader.e_lfanew);
41 		enforce(ntHeaders.Signature == IMAGE_NT_SIGNATURE, "Invalid NT signature");
42 		enforce(ntHeaders.FileHeader.Machine == IMAGE_FILE_MACHINE_I386, "Not an x86 PE");
43 
44 		dataDirectories = ntHeaders.OptionalHeader.DataDirectory.ptr[0..ntHeaders.OptionalHeader.NumberOfRvaAndSizes];
45 		enforce(cast(ubyte*)(dataDirectories.ptr + dataDirectories.length) <= data.ptr + data.length, "Not enough data for data directories");
46 
47 		auto sectionsStart = dosHeader.e_lfanew + ntHeaders.OptionalHeader.offsetof + ntHeaders.FileHeader.SizeOfOptionalHeader;
48 		auto sectionsEnd = sectionsStart + ntHeaders.FileHeader.NumberOfSections * IMAGE_SECTION_HEADER.sizeof;
49 		enforce(sectionsEnd <= data.length, "Not enough data for section headers");
50 		sectionHeaders = cast(IMAGE_SECTION_HEADER[])(data[sectionsStart .. sectionsEnd]);
51 	}
52 
53 	/// Translate a file offset to the relative virtual address
54 	/// (address relative to the image base).
55 	size_t fileToRva(size_t offset)
56 	{
57 		foreach (ref section; sectionHeaders)
58 			if (offset >= section.PointerToRawData && offset < section.PointerToRawData + section.SizeOfRawData)
59 				return offset - section.PointerToRawData + section.VirtualAddress;
60 		throw new Exception("Unmapped file offset");
61 	}
62 
63 	/// Reverse of fileToRva
64 	size_t rvaToFile(size_t offset)
65 	{
66 		foreach (ref section; sectionHeaders)
67 			if (offset >= section.VirtualAddress && offset < section.VirtualAddress + section.SizeOfRawData)
68 				return offset - section.VirtualAddress + section.PointerToRawData;
69 		throw new Exception("Unmapped memory address");
70 	}
71 
72 	/// Translate a file offset to the corresponding virtual address
73 	/// (the in-memory image address at the default image base).
74 	size_t fileToImage(size_t offset)
75 	{
76 		return fileToRva(offset) + ntHeaders.OptionalHeader.ImageBase;
77 	}
78 
79 	/// Reverse of fileToImage
80 	size_t imageToFile(size_t offset)
81 	{
82 		return rvaToFile(offset - ntHeaders.OptionalHeader.ImageBase);
83 	}
84 
85 	/// Provide an array-like view of the in-memory layout.
86 	@property auto imageData()
87 	{
88 		static struct ImageData
89 		{
90 			PE* pe;
91 
92 			ref ubyte opIndex(size_t offset)
93 			{
94 				return pe.data[pe.imageToFile(offset)];
95 			}
96 
97 			ubyte[] opSlice(size_t start, size_t end)
98 			{
99 				return pe.data[pe.imageToFile(start) .. pe.imageToFile(end)];
100 			}
101 
102 			T interpretAs(T)(size_t offset)
103 			{
104 				return *cast(T*)(pe.data.ptr + pe.imageToFile(offset));
105 			}
106 		}
107 
108 		return ImageData(&this);
109 	}
110 
111 	/// Get the image data for the given section header.
112 	ubyte[] sectionData(ref IMAGE_SECTION_HEADER section)
113 	{
114 		return data[section.PointerToRawData .. section.PointerToRawData + section.SizeOfRawData];
115 	}
116 
117 	/// Get the image data for the given directory entry.
118 	ubyte[] directoryData(USHORT entry)
119 	{
120 		auto dir = ntHeaders.OptionalHeader.DataDirectory[entry];
121 		auto b = rvaToFile(dir.VirtualAddress);
122 		return data[b .. b + dir.Size];
123 	}
124 }
125 
126 // UFCS helper
127 T interpretAs(T)(ubyte[] data, size_t offset)
128 {
129 	return *cast(T*)(data.ptr + offset);
130 }
131 
132 /// Get the name of an IMAGE_SECTION_HEADER as a D string.
133 @property string name(ref IMAGE_SECTION_HEADER section)
134 {
135 	auto n = cast(char[])section.Name[];
136 	auto p = n.indexOf(0);
137 	return n[0 .. p<0 ? $ : p].idup;
138 }