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 <ae@cy.md>
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 /// Parses a Portable Executable file.
23 struct PE
24 {
25 	ubyte[] data; /// File data bytes.
26 
27 	PIMAGE_DOS_HEADER dosHeader; /// Extracted headers.
28 	PIMAGE_NT_HEADERS32 ntHeaders; /// ditto
29 	IMAGE_SECTION_HEADER[] sectionHeaders; /// ditto
30 	IMAGE_DATA_DIRECTORY[] dataDirectories; /// ditto
31 
32 	this(void[] exe)
33 	{
34 		data = cast(ubyte[])exe;
35 
36 		enforce(data.length > IMAGE_DOS_HEADER.sizeof, "Not enough data for DOS header");
37 		dosHeader = cast(PIMAGE_DOS_HEADER)data.ptr;
38 		enforce(dosHeader.e_magic == IMAGE_DOS_SIGNATURE, "Invalid DOS signature");
39 
40 		enforce(data.length > dosHeader.e_lfanew + IMAGE_NT_HEADERS32.sizeof, "Not enough data for NT headers");
41 		ntHeaders = cast(PIMAGE_NT_HEADERS32)(data.ptr + dosHeader.e_lfanew);
42 		enforce(ntHeaders.Signature == IMAGE_NT_SIGNATURE, "Invalid NT signature");
43 		enforce(ntHeaders.FileHeader.Machine == IMAGE_FILE_MACHINE_I386, "Not an x86 PE");
44 
45 		dataDirectories = ntHeaders.OptionalHeader.DataDirectory.ptr[0..ntHeaders.OptionalHeader.NumberOfRvaAndSizes];
46 		enforce(cast(ubyte*)(dataDirectories.ptr + dataDirectories.length) <= data.ptr + data.length, "Not enough data for data directories");
47 
48 		auto sectionsStart = dosHeader.e_lfanew + ntHeaders.OptionalHeader.offsetof + ntHeaders.FileHeader.SizeOfOptionalHeader;
49 		auto sectionsEnd = sectionsStart + ntHeaders.FileHeader.NumberOfSections * IMAGE_SECTION_HEADER.sizeof;
50 		enforce(sectionsEnd <= data.length, "Not enough data for section headers");
51 		sectionHeaders = cast(IMAGE_SECTION_HEADER[])(data[sectionsStart .. sectionsEnd]);
52 	} ///
53 
54 	/// Translate a file offset to the relative virtual address
55 	/// (address relative to the image base).
56 	size_t fileToRva(size_t offset)
57 	{
58 		foreach (ref section; sectionHeaders)
59 			if (offset >= section.PointerToRawData && offset < section.PointerToRawData + section.SizeOfRawData)
60 				return offset - section.PointerToRawData + section.VirtualAddress;
61 		throw new Exception("Unmapped file offset");
62 	}
63 
64 	/// Reverse of fileToRva
65 	size_t rvaToFile(size_t offset)
66 	{
67 		foreach (ref section; sectionHeaders)
68 			if (offset >= section.VirtualAddress && offset < section.VirtualAddress + section.SizeOfRawData)
69 				return offset - section.VirtualAddress + section.PointerToRawData;
70 		throw new Exception("Unmapped memory address");
71 	}
72 
73 	/// Translate a file offset to the corresponding virtual address
74 	/// (the in-memory image address at the default image base).
75 	size_t fileToImage(size_t offset)
76 	{
77 		return fileToRva(offset) + ntHeaders.OptionalHeader.ImageBase;
78 	}
79 
80 	/// Reverse of fileToImage
81 	size_t imageToFile(size_t offset)
82 	{
83 		return rvaToFile(offset - ntHeaders.OptionalHeader.ImageBase);
84 	}
85 
86 	/// Provide an array-like view of the in-memory layout.
87 	@property auto imageData()
88 	{
89 		static struct ImageData
90 		{
91 			PE* pe;
92 
93 			ref ubyte opIndex(size_t offset)
94 			{
95 				return pe.data[pe.imageToFile(offset)];
96 			}
97 
98 			ubyte[] opSlice(size_t start, size_t end)
99 			{
100 				return pe.data[pe.imageToFile(start) .. pe.imageToFile(end)];
101 			}
102 
103 			T interpretAs(T)(size_t offset)
104 			{
105 				return *cast(T*)(pe.data.ptr + pe.imageToFile(offset));
106 			}
107 		}
108 
109 		return ImageData(&this);
110 	}
111 
112 	/// Get the image data for the given section header.
113 	ubyte[] sectionData(ref IMAGE_SECTION_HEADER section)
114 	{
115 		return data[section.PointerToRawData .. section.PointerToRawData + section.SizeOfRawData];
116 	}
117 
118 	/// Get the image data for the given directory entry.
119 	ubyte[] directoryData(USHORT entry)
120 	{
121 		auto dir = ntHeaders.OptionalHeader.DataDirectory[entry];
122 		auto b = rvaToFile(dir.VirtualAddress);
123 		return data[b .. b + dir.Size];
124 	}
125 }
126 
127 // UFCS helper
128 private T interpretAs(T)(ubyte[] data, size_t offset)
129 {
130 	return *cast(T*)(data.ptr + offset);
131 }
132 
133 /// Get the name of an IMAGE_SECTION_HEADER as a D string.
134 @property string name(ref IMAGE_SECTION_HEADER section)
135 {
136 	auto n = cast(char[])section.Name[];
137 	auto p = n.indexOf(0);
138 	return n[0 .. p<0 ? $ : p].idup;
139 }