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