1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Immutable;
6 using System.IO;
7 using System.Reflection.Internal;
8 using System.Reflection.Metadata;
9 using System.Reflection.Metadata.Ecma335;
10 
11 namespace System.Reflection.PortableExecutable
12 {
13     /// <summary>
14     /// An object used to read PE (Portable Executable) and COFF (Common Object File Format) headers from a stream.
15     /// </summary>
16     public sealed class PEHeaders
17     {
18         private readonly CoffHeader _coffHeader;
19         private readonly PEHeader _peHeader;
20         private readonly ImmutableArray<SectionHeader> _sectionHeaders;
21         private readonly CorHeader _corHeader;
22         private readonly bool _isLoadedImage;
23 
24         private readonly int _metadataStartOffset = -1;
25         private readonly int _metadataSize;
26         private readonly int _coffHeaderStartOffset = -1;
27         private readonly int _corHeaderStartOffset = -1;
28         private readonly int _peHeaderStartOffset = -1;
29 
30         internal const ushort DosSignature = 0x5A4D;     // 'M' 'Z'
31         internal const int PESignatureOffsetLocation = 0x3C;
32         internal const uint PESignature = 0x00004550;    // PE00
33         internal const int PESignatureSize = sizeof(uint);
34 
35         /// <summary>
36         /// Reads PE headers from the current location in the stream.
37         /// </summary>
38         /// <param name="peStream">Stream containing PE image starting at the stream's current position and ending at the end of the stream.</param>
39         /// <exception cref="BadImageFormatException">The data read from stream have invalid format.</exception>
40         /// <exception cref="IOException">Error reading from the stream.</exception>
41         /// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
42         /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
PEHeaders(Stream peStream)43         public PEHeaders(Stream peStream)
44            : this(peStream, 0)
45         {
46         }
47 
48         /// <summary>
49         /// Reads PE headers from the current location in the stream.
50         /// </summary>
51         /// <param name="peStream">Stream containing PE image of the given size starting at its current position.</param>
52         /// <param name="size">Size of the PE image.</param>
53         /// <exception cref="BadImageFormatException">The data read from stream have invalid format.</exception>
54         /// <exception cref="IOException">Error reading from the stream.</exception>
55         /// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
56         /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
57         /// <exception cref="ArgumentOutOfRangeException">Size is negative or extends past the end of the stream.</exception>
PEHeaders(Stream peStream, int size)58         public PEHeaders(Stream peStream, int size)
59             : this(peStream, size, isLoadedImage: false)
60         {
61         }
62 
63         /// <summary>
64         /// Reads PE headers from the current location in the stream.
65         /// </summary>
66         /// <param name="peStream">Stream containing PE image of the given size starting at its current position.</param>
67         /// <param name="size">Size of the PE image.</param>
68         /// <param name="isLoadedImage">True if the PE image has been loaded into memory by the OS loader.</param>
69         /// <exception cref="BadImageFormatException">The data read from stream have invalid format.</exception>
70         /// <exception cref="IOException">Error reading from the stream.</exception>
71         /// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
72         /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
73         /// <exception cref="ArgumentOutOfRangeException">Size is negative or extends past the end of the stream.</exception>
PEHeaders(Stream peStream, int size, bool isLoadedImage)74         public PEHeaders(Stream peStream, int size, bool isLoadedImage)
75         {
76             if (peStream == null)
77             {
78                 throw new ArgumentNullException(nameof(peStream));
79             }
80 
81             if (!peStream.CanRead || !peStream.CanSeek)
82             {
83                 throw new ArgumentException(SR.StreamMustSupportReadAndSeek, nameof(peStream));
84             }
85 
86             _isLoadedImage = isLoadedImage;
87 
88             int actualSize = StreamExtensions.GetAndValidateSize(peStream, size, nameof(peStream));
89             var reader = new PEBinaryReader(peStream, actualSize);
90 
91             bool isCoffOnly;
92             SkipDosHeader(ref reader, out isCoffOnly);
93 
94             _coffHeaderStartOffset = reader.CurrentOffset;
95             _coffHeader = new CoffHeader(ref reader);
96 
97             if (!isCoffOnly)
98             {
99                 _peHeaderStartOffset = reader.CurrentOffset;
100                 _peHeader = new PEHeader(ref reader);
101             }
102 
103             _sectionHeaders = this.ReadSectionHeaders(ref reader);
104 
105             if (!isCoffOnly)
106             {
107                 int offset;
108                 if (TryCalculateCorHeaderOffset(actualSize, out offset))
109                 {
110                     _corHeaderStartOffset = offset;
111                     reader.Seek(offset);
112                     _corHeader = new CorHeader(ref reader);
113                 }
114             }
115 
116             CalculateMetadataLocation(actualSize, out _metadataStartOffset, out _metadataSize);
117         }
118 
119         /// <summary>
120         /// Gets the offset (in bytes) from the start of the PE image to the start of the CLI metadata.
121         /// or -1 if the image does not contain metadata.
122         /// </summary>
123         public int MetadataStartOffset
124         {
125             get { return _metadataStartOffset; }
126         }
127 
128         /// <summary>
129         /// Gets the size of the CLI metadata 0 if the image does not contain metadata.)
130         /// </summary>
131         public int MetadataSize
132         {
133             get { return _metadataSize; }
134         }
135 
136         /// <summary>
137         /// Gets the COFF header of the image.
138         /// </summary>
139         public CoffHeader CoffHeader
140         {
141             get { return _coffHeader; }
142         }
143 
144         /// <summary>
145         /// Gets the byte offset from the start of the PE image to the start of the COFF header.
146         /// </summary>
147         public int CoffHeaderStartOffset
148         {
149             get { return _coffHeaderStartOffset; }
150         }
151 
152         /// <summary>
153         /// Determines if the image is Coff only.
154         /// </summary>
155         public bool IsCoffOnly
156         {
157             get { return _peHeader == null; }
158         }
159 
160         /// <summary>
161         /// Gets the PE header of the image or null if the image is COFF only.
162         /// </summary>
163         public PEHeader PEHeader
164         {
165             get { return _peHeader; }
166         }
167 
168         /// <summary>
169         /// Gets the byte offset from the start of the image to
170         /// </summary>
171         public int PEHeaderStartOffset
172         {
173             get { return _peHeaderStartOffset; }
174         }
175 
176         /// <summary>
177         /// Gets the PE section headers.
178         /// </summary>
179         public ImmutableArray<SectionHeader> SectionHeaders
180         {
181             get { return _sectionHeaders; }
182         }
183 
184         /// <summary>
185         /// Gets the CLI header or null if the image does not have one.
186         /// </summary>
187         public CorHeader CorHeader
188         {
189             get { return _corHeader; }
190         }
191 
192         /// <summary>
193         /// Gets the byte offset from the start of the image to the COR header or -1 if the image does not have one.
194         /// </summary>
195         public int CorHeaderStartOffset
196         {
197             get { return _corHeaderStartOffset; }
198         }
199 
200         /// <summary>
201         /// Determines if the image represents a Windows console application.
202         /// </summary>
203         public bool IsConsoleApplication
204         {
205             get
206             {
207                 return _peHeader != null && _peHeader.Subsystem == Subsystem.WindowsCui;
208             }
209         }
210 
211         /// <summary>
212         /// Determines if the image represents a dynamically linked library.
213         /// </summary>
214         public bool IsDll
215         {
216             get
217             {
218                 return (_coffHeader.Characteristics & Characteristics.Dll) != 0;
219             }
220         }
221 
222         /// <summary>
223         /// Determines if the image represents an executable.
224         /// </summary>
225         public bool IsExe
226         {
227             get
228             {
229                 return (_coffHeader.Characteristics & Characteristics.Dll) == 0;
230             }
231         }
232 
TryCalculateCorHeaderOffset(long peStreamSize, out int startOffset)233         private bool TryCalculateCorHeaderOffset(long peStreamSize, out int startOffset)
234         {
235             if (!TryGetDirectoryOffset(_peHeader.CorHeaderTableDirectory, out startOffset, canCrossSectionBoundary: false))
236             {
237                 startOffset = -1;
238                 return false;
239             }
240 
241             int length = _peHeader.CorHeaderTableDirectory.Size;
242             if (length < COR20Constants.SizeOfCorHeader)
243             {
244                 throw new BadImageFormatException(SR.InvalidCorHeaderSize);
245             }
246 
247             return true;
248         }
249 
SkipDosHeader(ref PEBinaryReader reader, out bool isCOFFOnly)250         private void SkipDosHeader(ref PEBinaryReader reader, out bool isCOFFOnly)
251         {
252             // Look for DOS Signature "MZ"
253             ushort dosSig = reader.ReadUInt16();
254 
255             if (dosSig != DosSignature)
256             {
257                 // If image doesn't start with DOS signature, let's assume it is a
258                 // COFF (Common Object File Format), aka .OBJ file.
259                 // See CLiteWeightStgdbRW::FindObjMetaData in ndp\clr\src\MD\enc\peparse.cpp
260 
261                 if (dosSig != 0 || reader.ReadUInt16() != 0xffff)
262                 {
263                     isCOFFOnly = true;
264                     reader.Seek(0);
265                 }
266                 else
267                 {
268                     // Might need to handle other formats. Anonymous or LTCG objects, for example.
269                     throw new BadImageFormatException(SR.UnknownFileFormat);
270                 }
271             }
272             else
273             {
274                 isCOFFOnly = false;
275             }
276 
277             if (!isCOFFOnly)
278             {
279                 // Skip the DOS Header
280                 reader.Seek(PESignatureOffsetLocation);
281 
282                 int ntHeaderOffset = reader.ReadInt32();
283                 reader.Seek(ntHeaderOffset);
284 
285                 // Look for PESignature "PE\0\0"
286                 uint ntSignature = reader.ReadUInt32();
287                 if (ntSignature != PESignature)
288                 {
289                     throw new BadImageFormatException(SR.InvalidPESignature);
290                 }
291             }
292         }
293 
ReadSectionHeaders(ref PEBinaryReader reader)294         private ImmutableArray<SectionHeader> ReadSectionHeaders(ref PEBinaryReader reader)
295         {
296             int numberOfSections = _coffHeader.NumberOfSections;
297             if (numberOfSections < 0)
298             {
299                 throw new BadImageFormatException(SR.InvalidNumberOfSections);
300             }
301 
302             var builder = ImmutableArray.CreateBuilder<SectionHeader>(numberOfSections);
303 
304             for (int i = 0; i < numberOfSections; i++)
305             {
306                 builder.Add(new SectionHeader(ref reader));
307             }
308 
309             return builder.ToImmutable();
310         }
311 
312         /// <summary>
313         /// Gets the offset (in bytes) from the start of the image to the given directory data.
314         /// </summary>
315         /// <param name="directory">PE directory entry</param>
316         /// <param name="offset">Offset from the start of the image to the given directory data</param>
317         /// <returns>True if the directory data is found, false otherwise.</returns>
TryGetDirectoryOffset(DirectoryEntry directory, out int offset)318         public bool TryGetDirectoryOffset(DirectoryEntry directory, out int offset)
319         {
320             return TryGetDirectoryOffset(directory, out offset, canCrossSectionBoundary: true);
321         }
322 
TryGetDirectoryOffset(DirectoryEntry directory, out int offset, bool canCrossSectionBoundary)323         internal bool TryGetDirectoryOffset(DirectoryEntry directory, out int offset, bool canCrossSectionBoundary)
324         {
325             int sectionIndex = GetContainingSectionIndex(directory.RelativeVirtualAddress);
326             if (sectionIndex < 0)
327             {
328                 offset = -1;
329                 return false;
330             }
331 
332             int relativeOffset = directory.RelativeVirtualAddress - _sectionHeaders[sectionIndex].VirtualAddress;
333             if (!canCrossSectionBoundary && directory.Size > _sectionHeaders[sectionIndex].VirtualSize - relativeOffset)
334             {
335                 throw new BadImageFormatException(SR.SectionTooSmall);
336             }
337 
338             offset = _isLoadedImage ? directory.RelativeVirtualAddress : _sectionHeaders[sectionIndex].PointerToRawData + relativeOffset;
339             return true;
340         }
341 
342         /// <summary>
343         /// Searches sections of the PE image for the one that contains specified Relative Virtual Address.
344         /// </summary>
345         /// <param name="relativeVirtualAddress">Address.</param>
346         /// <returns>
347         /// Index of the section that contains <paramref name="relativeVirtualAddress"/>,
348         /// or -1 if there is none.
349         /// </returns>
GetContainingSectionIndex(int relativeVirtualAddress)350         public int GetContainingSectionIndex(int relativeVirtualAddress)
351         {
352             for (int i = 0; i < _sectionHeaders.Length; i++)
353             {
354                 if (_sectionHeaders[i].VirtualAddress <= relativeVirtualAddress &&
355                     relativeVirtualAddress < _sectionHeaders[i].VirtualAddress + _sectionHeaders[i].VirtualSize)
356                 {
357                     return i;
358                 }
359             }
360 
361             return -1;
362         }
363 
IndexOfSection(string name)364         internal int IndexOfSection(string name)
365         {
366             for (int i = 0; i < SectionHeaders.Length; i++)
367             {
368                 if (SectionHeaders[i].Name.Equals(name, StringComparison.Ordinal))
369                 {
370                     return i;
371                 }
372             }
373 
374             return -1;
375         }
376 
CalculateMetadataLocation(long peImageSize, out int start, out int size)377         private void CalculateMetadataLocation(long peImageSize, out int start, out int size)
378         {
379             if (IsCoffOnly)
380             {
381                 int cormeta = IndexOfSection(".cormeta");
382                 if (cormeta == -1)
383                 {
384                     start = -1;
385                     size = 0;
386                     return;
387                 }
388 
389                 if (_isLoadedImage)
390                 {
391                     start = SectionHeaders[cormeta].VirtualAddress;
392                     size = SectionHeaders[cormeta].VirtualSize;
393                 }
394                 else
395                 {
396                     start = SectionHeaders[cormeta].PointerToRawData;
397                     size = SectionHeaders[cormeta].SizeOfRawData;
398                 }
399             }
400             else if (_corHeader == null)
401             {
402                 start = 0;
403                 size = 0;
404                 return;
405             }
406             else
407             {
408                 if (!TryGetDirectoryOffset(_corHeader.MetadataDirectory, out start, canCrossSectionBoundary: false))
409                 {
410                     throw new BadImageFormatException(SR.MissingDataDirectory);
411                 }
412 
413                 size = _corHeader.MetadataDirectory.Size;
414             }
415 
416             if (start < 0 ||
417                 start >= peImageSize ||
418                 size <= 0 ||
419                 start > peImageSize - size)
420             {
421                 throw new BadImageFormatException(SR.InvalidMetadataSectionSpan);
422             }
423         }
424     }
425 }
426