diff --git a/src/LibObjectFile.Tests/PE/PEReaderTests.cs b/src/LibObjectFile.Tests/PE/PEReaderTests.cs index 84d06f8..fcaa4b3 100644 --- a/src/LibObjectFile.Tests/PE/PEReaderTests.cs +++ b/src/LibObjectFile.Tests/PE/PEReaderTests.cs @@ -4,13 +4,10 @@ using System; using System.IO; -using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using LibObjectFile.Diagnostics; using LibObjectFile.PE; using VerifyMSTest; -using VerifyTests; namespace LibObjectFile.Tests.PE; @@ -49,4 +46,33 @@ public async Task TestPrinter(string name) await Verifier.Verify(afterUpdateText).UseParameters(name).DisableRequireUniquePrefix(); } } + + [TestMethod] + public async Task TestTinyExe97Bytes() + { + // http://www.phreedom.org/research/tinype/ + // TinyPE: The smallest possible PE file + // 97 bytes + byte[] data = + [ + 0x4D, 0x5A, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x01, 0x00, 0x6A, 0x2A, 0x58, 0xC3, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x01, 0x0B, 0x01, 0x08, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02 + ]; + + var stream = new MemoryStream(); + stream.Write(data, 0, data.Length); + stream.Position = 0; + + var peImage = PEFile.Read(stream); + var writer = new StringWriter(); + peImage.Print(writer); + var afterReadText = writer.ToString(); + + await Verifier.Verify(afterReadText); + } } \ No newline at end of file diff --git a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsole2Win64.exe.verified.txt b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsole2Win64.exe.verified.txt index b8a3e31..83c1694 100644 --- a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsole2Win64.exe.verified.txt +++ b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsole2Win64.exe.verified.txt @@ -79,6 +79,7 @@ Data Directories [12] = PEImportAddressTableDirectory Position = 0x00001E00, Size = 0x00000250, RVA = 0x00003000, VirtualSize = 0x00000250 [13] = PEDelayImportDirectory Position = 0x000029A8, Size = 0x00000040, RVA = 0x00003BA8, VirtualSize = 0x00000040 [14] = null + [15] = null Section Headers [00] .text PESection Position = 0x00000400, Size = 0x00001A00, RVA = 0x00001000, VirtualSize = 0x000019E9, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead) diff --git a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsoleWin64.exe.verified.txt b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsoleWin64.exe.verified.txt index 87af661..1d430df 100644 --- a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsoleWin64.exe.verified.txt +++ b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsoleWin64.exe.verified.txt @@ -79,6 +79,7 @@ Data Directories [12] = PEImportAddressTableDirectory Position = 0x00001600, Size = 0x000001F0, RVA = 0x00003000, VirtualSize = 0x000001F0 [13] = null [14] = null + [15] = null Section Headers [00] .text PESection Position = 0x00000400, Size = 0x00001200, RVA = 0x00001000, VirtualSize = 0x000010C9, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead) diff --git a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeLibraryWin64.dll.verified.txt b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeLibraryWin64.dll.verified.txt index ba98b56..b38a9e1 100644 --- a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeLibraryWin64.dll.verified.txt +++ b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeLibraryWin64.dll.verified.txt @@ -79,6 +79,7 @@ Data Directories [12] = PEImportAddressTableDirectory Position = 0x00001400, Size = 0x000000E8, RVA = 0x00002000, VirtualSize = 0x000000E8 [13] = null [14] = null + [15] = null Section Headers [00] .text PESection Position = 0x00000400, Size = 0x00001000, RVA = 0x00001000, VirtualSize = 0x00000F18, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead) diff --git a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestTinyExe97Bytes.verified.txt b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestTinyExe97Bytes.verified.txt new file mode 100644 index 0000000..062ad5d --- /dev/null +++ b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestTinyExe97Bytes.verified.txt @@ -0,0 +1,74 @@ +DOS Header + Magic = DOS + ByteCountOnLastPage = 0x0 + PageCount = 0x4550 + RelocationCount = 0x0 + SizeOfParagraphsHeader = 0x14C + MinExtraParagraphs = 0x1 + MaxExtraParagraphs = 0x2A6A + InitialSSValue = 0xC358 + InitialSPValue = 0x0 + Checksum = 0x0 + InitialIPValue = 0x0 + InitialCSValue = 0x0 + FileAddressRelocationTable = 0x4 + OverlayNumber = 0x103 + Reserved = 0x10B, 0x8, 0x4, 0x0 + OEMIdentifier = 0x0 + OEMInformation = 0x0 + Reserved2 = 0x4, 0x0, 0xC, 0x0, 0x4, 0x0, 0xC, 0x0, 0x0, 0x40 + FileAddressPEHeader = 0x4 + +DOS Stub + DosStub = 0 bytes + +COFF Header + Machine = I386 + NumberOfSections = 1 + TimeDateStamp = 3277335146 + PointerToSymbolTable = 0x0 + NumberOfSymbols = 0 + SizeOfOptionalHeader = 4 + Characteristics = RelocsStripped, ExecutableImage, Bit32Machine + +Optional Header + Magic = PE32 + MajorLinkerVersion = 8 + MinorLinkerVersion = 0 + SizeOfCode = 0x4 + SizeOfInitializedData = 0x0 + SizeOfUninitializedData = 0x4 + AddressOfEntryPoint = 0xC + BaseOfCode = 0x4 + BaseOfData = 0xC + ImageBase = 0x0 + SectionAlignment = 0x4 + FileAlignment = 0x4 + MajorOperatingSystemVersion = 4 + MinorOperatingSystemVersion = 0 + MajorImageVersion = 0 + MinorImageVersion = 0 + MajorSubsystemVersion = 4 + MinorSubsystemVersion = 0 + Win32VersionValue = 0x0 + SizeOfImage = 0x68 + SizeOfHeaders = 0x64 + CheckSum = 0x0 + Subsystem = WindowsGui + DllCharacteristics = 0 + SizeOfStackReserve = 0x0 + SizeOfStackCommit = 0x0 + SizeOfHeapReserve = 0x0 + SizeOfHeapCommit = 0x0 + LoaderFlags = 0x0 + NumberOfRvaAndSizes = 0x0 + +Section Headers + [00]  PESection Position = 0x0000000C, Size = 0x00000004, RVA = 0x0000000C, VirtualSize = 0x00000004, Characteristics = 0x00000004 (TypeGroup) + +Sections + -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [00]  PESection Position = 0x0000000C, Size = 0x00000004, RVA = 0x0000000C, VirtualSize = 0x00000004, Characteristics = 0x00000004 (TypeGroup) + + [00] PEStreamSectionData Position = 0x0000000C, Size = 0x00000004, RVA = 0x0000000C, VirtualSize = 0x00000004 + diff --git a/src/LibObjectFile/Diagnostics/DiagnosticId.cs b/src/LibObjectFile/Diagnostics/DiagnosticId.cs index df33aec..392b5e4 100644 --- a/src/LibObjectFile/Diagnostics/DiagnosticId.cs +++ b/src/LibObjectFile/Diagnostics/DiagnosticId.cs @@ -130,8 +130,10 @@ public enum DiagnosticId PE_ERR_TooManySections = 3012, PE_ERR_FileAlignmentNotPowerOfTwo = 3013, PE_ERR_SectionAlignmentNotPowerOfTwo = 3014, - PE_ERR_SectionAlignmentLessThanFileAlignment = 315, - + PE_ERR_SectionAlignmentLessThanFileAlignment = 3015, + PE_ERR_InvalidPEHeaderPosition = 3016, + PE_ERR_InvalidNumberOfDataDirectories = 3017, + // PE Exception directory PE_ERR_InvalidExceptionDirectory_Entries = 3100, PE_ERR_InvalidExceptionDirectory_Entry = 3101, diff --git a/src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs index a9bec2f..dcda8a6 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs @@ -46,7 +46,7 @@ internal static PEDataDirectory Create(PEDataDirectoryKind kind, bool is32Bits) PEDataDirectoryKind.DelayImport => new PEDelayImportDirectory(), PEDataDirectoryKind.ImportAddressTable => new PEImportAddressTableDirectory(), PEDataDirectoryKind.ClrMetadata => new PEClrMetadata(), - _ => throw new ArgumentOutOfRangeException(nameof(kind)) + _ => new PEUnknownDirectory((int)kind) }; } -} +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs b/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs index 2c8614a..3bf8495 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Reflection; using System.Runtime.CompilerServices; namespace LibObjectFile.PE; @@ -17,20 +18,81 @@ namespace LibObjectFile.PE; [DebuggerDisplay($"{nameof(PEDirectoryTable)} {nameof(Count)} = {{{nameof(Count)}}}")] public sealed class PEDirectoryTable : IEnumerable { - private InternalArray _entries; + private PEObjectBase?[] _entries; private int _count; internal PEDirectoryTable() { + _entries = []; } - public PEObjectBase? this[PEDataDirectoryKind kind] => _entries[(int)kind]; + /// + /// Gets the directory entry at the specified index. + /// + /// The index of the directory entry to get. + /// The directory entry at the specified index. + /// Thrown if the index is out of range. + public PEObjectBase? this[int index] + { + get + { + if (index < 0 || index >= _count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + return _entries[index]; + } + } /// - /// Gets the number of directory entries in the array. + /// Gets the directory entry of the specified kind. Must be within the bounds of . /// - public int Count => _count; - + /// The kind of directory entry to get. + /// + /// Thrown if the kind is out of range. + public PEObjectBase? this[PEDataDirectoryKind kind] + { + get + { + int index = (int)(ushort)kind; + if (index < 0 || index >= _count) + { + throw new ArgumentOutOfRangeException(nameof(kind)); + } + return _entries[(int)kind]; + } + } + + /// + /// Gets the maximum number of directory entries in the array. + /// + public int Count + { + get => _count; + + set + { + ArgumentOutOfRangeException.ThrowIfLessThan(value, 0); + + var previousCount = _count; + // If the count is reduced, we need to check that all entries are null after + for (int i = value; i < previousCount; i++) + { + if (_entries[i] is not null) + { + throw new ArgumentOutOfRangeException(nameof(value), $"A non null directory entry was found at index {i}. This directory entry must be removed before setting a count of {value}"); + } + } + + if (_entries.Length < value) + { + Array.Resize(ref _entries, value); + } + + _count = value; + } + } + /// /// Gets the export directory information from the PE file. /// @@ -112,60 +174,23 @@ internal PEDirectoryTable() [EditorBrowsable(EditorBrowsableState.Never)] public Enumerator GetEnumerator() => new(this); - internal void Set(PESecurityCertificateDirectory? directory) - { - var kind = PEDataDirectoryKind.SecurityCertificate; - ref var entry = ref _entries[(int)kind]; - var previousEntry = entry; - entry = directory; + internal void Set(PESecurityCertificateDirectory? directory) => Set(PEDataDirectoryKind.SecurityCertificate, directory); - if (previousEntry is not null) - { - _count--; - } + internal void Set(PEDataDirectoryKind kind, PEObjectBase? directory) => Set((int)kind, directory); - if (directory is not null) - { - _count++; - } - } - - internal void Set(PEDataDirectoryKind kind, PEDataDirectory? directory) + internal void Set(int index, PEObjectBase? directory) { - ref var entry = ref _entries[(int)kind]; - var previousEntry = entry; - entry = directory; - - if (previousEntry is not null) + if (index >= Count) { - _count--; + throw new ArgumentOutOfRangeException(nameof(index), $"The directory entry only accepts {Count} entries. Set the count explicitly to allow more entries."); } - if (directory is not null) - { - _count++; - } + _entries[index] = directory; } - - internal int CalculateNumberOfEntries() - { - int count = 0; - ReadOnlySpan span = _entries; - for(int i = 0; i < span.Length; i++) - { - if (_entries[i] is not null) - { - count = i + 1; - } - } - - return count; - } - + internal unsafe void Write(PEImageWriter writer, ref uint position) { - var numberOfEntries = CalculateNumberOfEntries(); - for (int i = 0; i < numberOfEntries; i++) + for (int i = 0; i < Count; i++) { ImageDataDirectory rawDataDirectory = default; var entry = _entries[i]; @@ -176,13 +201,7 @@ internal unsafe void Write(PEImageWriter writer, ref uint position) } } - position += (uint)(numberOfEntries * sizeof(ImageDataDirectory)); - } - - [InlineArray(15)] - private struct InternalArray - { - private PEObjectBase? _element; + position += (uint)(Count * sizeof(ImageDataDirectory)); } /// diff --git a/src/LibObjectFile/PE/DataDirectory/PESecurityCertificateDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PESecurityCertificateDirectory.cs index 3f5cb3f..edf4221 100644 --- a/src/LibObjectFile/PE/DataDirectory/PESecurityCertificateDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PESecurityCertificateDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -71,9 +71,9 @@ public override unsafe void Read(PEImageReader reader) { Revision = header.Revision, Type = header.Type, + Data = reader.ReadAsStream(header.Length - 8) }; - certificate.Data = reader.ReadAsStream(header.Length - 8); Certificates.Add(certificate); if (reader.HasErrors) diff --git a/src/LibObjectFile/PE/DataDirectory/PEUnknownDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEUnknownDirectory.cs new file mode 100644 index 0000000..1705990 --- /dev/null +++ b/src/LibObjectFile/PE/DataDirectory/PEUnknownDirectory.cs @@ -0,0 +1,17 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +namespace LibObjectFile.PE; + +/// +/// Represents an unknown directory (when going beyond the known directories). +/// +public sealed class PEUnknownDirectory : PEDataDirectory +{ + internal PEUnknownDirectory(int index) : base((PEDataDirectoryKind)index) + { + } + + protected override uint ComputeHeaderSize(PELayoutContext context) => 0; +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/ImageCoffHeader.cs b/src/LibObjectFile/PE/ImageCoffHeader.cs index e0c03d5..d1f1575 100644 --- a/src/LibObjectFile/PE/ImageCoffHeader.cs +++ b/src/LibObjectFile/PE/ImageCoffHeader.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -19,30 +19,46 @@ public struct ImageCoffHeader /// public System.Reflection.PortableExecutable.Machine Machine; + internal ushort _NumberOfSections; + /// /// The number of sections in the file. /// - public ushort NumberOfSections; + /// + /// This value is readonly as it will be updated by and the number of sections will be computed from the sections list. + /// + public ushort NumberOfSections => _NumberOfSections; /// /// The low 32 bits of the time stamp indicating when the file was created. /// public uint TimeDateStamp; + internal uint _PointerToSymbolTable; + /// /// The file pointer to the COFF symbol table. This is zero if no symbol table is present. /// - public uint PointerToSymbolTable; + /// This value is readonly and is zero for a PE Image file. + public uint PointerToSymbolTable => _PointerToSymbolTable; + + internal uint _NumberOfSymbols; /// /// The number of entries in the symbol table. /// - public uint NumberOfSymbols; + /// This value is readonly and is zero for a PE Image file. + public uint NumberOfSymbols => _NumberOfSymbols; + + internal ushort _SizeOfOptionalHeader; /// /// The size of the optional header, which is required for executable files but not for object files. /// - public ushort SizeOfOptionalHeader; + /// + /// This value is readonly as it will be updated automatically when reading or updating the layout of the PE file. + /// + public ushort SizeOfOptionalHeader => _SizeOfOptionalHeader; /// /// The characteristics of the file that define its properties, such as whether it's an executable, a DLL, etc. diff --git a/src/LibObjectFile/PE/ImageDosHeader.cs b/src/LibObjectFile/PE/ImageDosHeader.cs index a8f297d..69ad31c 100644 --- a/src/LibObjectFile/PE/ImageDosHeader.cs +++ b/src/LibObjectFile/PE/ImageDosHeader.cs @@ -103,8 +103,11 @@ public unsafe struct ImageDosHeader /// public fixed ushort Reserved2[10]; + internal uint _FileAddressPEHeader; + /// /// File address of new exe header. (Original DOS field is `e_lfanew`) /// - public uint FileAddressPEHeader; + /// This property is automatically calculated but can be slightly adjusted with + public uint FileAddressPEHeader => _FileAddressPEHeader; } \ No newline at end of file diff --git a/src/LibObjectFile/PE/ImageOptionalHeader.cs b/src/LibObjectFile/PE/ImageOptionalHeader.cs index cb4d0af..7375e03 100644 --- a/src/LibObjectFile/PE/ImageOptionalHeader.cs +++ b/src/LibObjectFile/PE/ImageOptionalHeader.cs @@ -1,8 +1,10 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using System; using System.Diagnostics.CodeAnalysis; +using System.Numerics; using LibObjectFile.PE.Internal; namespace LibObjectFile.PE; @@ -20,10 +22,12 @@ public struct ImageOptionalHeader /// /// The magic number, which identifies the file format. Expected to be 0x10b for PE32. /// + /// + /// This value cannot be changed and must be set at construction time. + /// public ImageOptionalHeaderMagic Magic { get => OptionalHeaderCommonPart1.Magic; - set => OptionalHeaderCommonPart1.Magic = value; } /// @@ -50,7 +54,6 @@ public byte MinorLinkerVersion public uint SizeOfCode { get => OptionalHeaderCommonPart1.SizeOfCode; - set => OptionalHeaderCommonPart1.SizeOfCode = value; } /// @@ -59,7 +62,6 @@ public uint SizeOfCode public uint SizeOfInitializedData { get => OptionalHeaderCommonPart1.SizeOfInitializedData; - set => OptionalHeaderCommonPart1.SizeOfInitializedData = value; } /// @@ -68,7 +70,6 @@ public uint SizeOfInitializedData public uint SizeOfUninitializedData { get => OptionalHeaderCommonPart1.SizeOfUninitializedData; - set => OptionalHeaderCommonPart1.SizeOfUninitializedData = value; } /// @@ -86,7 +87,6 @@ public uint AddressOfEntryPoint public uint BaseOfCode { get => OptionalHeaderCommonPart1.BaseOfCode; - set => OptionalHeaderCommonPart1.BaseOfCode = value; } /// @@ -98,7 +98,6 @@ public uint BaseOfCode public uint BaseOfData { get => OptionalHeaderBase32.BaseOfData; - set => OptionalHeaderBase32.BaseOfData = value; } // NT additional fields. @@ -106,10 +105,12 @@ public uint BaseOfData /// /// The preferred address of the first byte of the image when loaded into memory. /// + /// + /// In order to change the ImageBase use + /// public ulong ImageBase { get => OptionalHeaderBase64.ImageBase; - set => OptionalHeaderBase64.ImageBase = value; } /// @@ -118,7 +119,20 @@ public ulong ImageBase public uint SectionAlignment { get => OptionalHeaderCommonPart2.SectionAlignment; - set => OptionalHeaderCommonPart2.SectionAlignment = value; + set + { + if (value == 0 || !BitOperations.IsPow2(value)) + { + throw new ArgumentOutOfRangeException(nameof(value), "SectionAlignment must be a power of 2 and not zero"); + } + + if (SectionAlignment < FileAlignment) + { + throw new ArgumentOutOfRangeException(nameof(value), "SectionAlignment must be greater than or equal to FileAlignment"); + } + + OptionalHeaderCommonPart2.SectionAlignment = value; + } } /// @@ -127,7 +141,15 @@ public uint SectionAlignment public uint FileAlignment { get => OptionalHeaderCommonPart2.FileAlignment; - set => OptionalHeaderCommonPart2.FileAlignment = value; + set + { + if (value == 0 || !BitOperations.IsPow2(value)) + { + throw new ArgumentOutOfRangeException(nameof(value), "FileAlignment must be a power of 2 and not zero"); + } + + OptionalHeaderCommonPart2.FileAlignment = value; + } } /// @@ -199,7 +221,6 @@ public uint Win32VersionValue public uint SizeOfImage { get => OptionalHeaderCommonPart2.SizeOfImage; - set => OptionalHeaderCommonPart2.SizeOfImage = value; } /// @@ -208,7 +229,6 @@ public uint SizeOfImage public uint SizeOfHeaders { get => OptionalHeaderCommonPart2.SizeOfHeaders; - set => OptionalHeaderCommonPart2.SizeOfHeaders = value; } /// @@ -289,7 +309,6 @@ public uint LoaderFlags public uint NumberOfRvaAndSizes { get => OptionalHeaderCommonPart3.NumberOfRvaAndSizes; - set => OptionalHeaderCommonPart3.NumberOfRvaAndSizes = value; } /// diff --git a/src/LibObjectFile/PE/Internal/RawImageOptionalHeader32.cs b/src/LibObjectFile/PE/Internal/RawImageOptionalHeader32.cs index febb7a6..19b1b42 100644 --- a/src/LibObjectFile/PE/Internal/RawImageOptionalHeader32.cs +++ b/src/LibObjectFile/PE/Internal/RawImageOptionalHeader32.cs @@ -16,5 +16,8 @@ internal struct RawImageOptionalHeader32 public RawImageOptionalHeaderCommonPart2 Common2; public RawImageOptionalHeaderSize32 Size32; public RawImageOptionalHeaderCommonPart3 Common3; + + // In case of a PE Header with zero directories + public static unsafe int MinimumSize => sizeof(RawImageOptionalHeader32) - sizeof(ImageDataDirectoryArray); } diff --git a/src/LibObjectFile/PE/Internal/RawImageOptionalHeader64.cs b/src/LibObjectFile/PE/Internal/RawImageOptionalHeader64.cs index 83de80e..fb04b5c 100644 --- a/src/LibObjectFile/PE/Internal/RawImageOptionalHeader64.cs +++ b/src/LibObjectFile/PE/Internal/RawImageOptionalHeader64.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -16,4 +16,7 @@ internal struct RawImageOptionalHeader64 public RawImageOptionalHeaderCommonPart2 Common2; public RawImageOptionalHeaderSize64 Size64; public RawImageOptionalHeaderCommonPart3 Common3; + + // In case of a PE Header with zero directories + public static unsafe int MinimumSize => sizeof(RawImageOptionalHeader64) - sizeof(ImageDataDirectoryArray); } diff --git a/src/LibObjectFile/PE/PEFile.Read.cs b/src/LibObjectFile/PE/PEFile.Read.cs index 3690eba..8c73d42 100644 --- a/src/LibObjectFile/PE/PEFile.Read.cs +++ b/src/LibObjectFile/PE/PEFile.Read.cs @@ -57,7 +57,7 @@ public static bool TryRead(Stream stream, [NotNullWhen(true)] out PEFile? peFile return !reader.Diagnostics.HasErrors; } - public override void Read(PEImageReader reader) + public override unsafe void Read(PEImageReader reader) { Debug.Assert(Unsafe.SizeOf() == 64); @@ -75,37 +75,58 @@ public override void Read(PEImageReader reader) return; } - // Read the DOS stub - var dosStubSize = DosHeader.SizeOfParagraphsHeader * 16; - if (dosStubSize > 0) + var pePosition = DosHeader.FileAddressPEHeader; + + if (pePosition < sizeof(ImageDosHeader)) { - var dosStub = new byte[dosStubSize]; - read = reader.Read(dosStub); - if (read != dosStubSize) + if (pePosition < 4) { - diagnostics.Error(DiagnosticId.PE_ERR_InvalidDosStubSize, "Invalid DOS stub"); + diagnostics.Error(DiagnosticId.PE_ERR_InvalidPEHeaderPosition, "Invalid PE header position"); + return; } - _dosStub = dosStub; + _unsafeNegativePEHeaderOffset = (int)pePosition - sizeof(ImageDosHeader); } else { - _dosStub = []; - } + // Read the DOS stub + var dosStubSize = DosHeader.SizeOfParagraphsHeader * 16; - read = (int)reader.Position; - // Read any DOS stub extra data (e.g Rich) - if (DosHeader.FileAddressPEHeader > read) - { - _dosStubExtra = reader.ReadAsStream((ulong)(DosHeader.FileAddressPEHeader - read)); - } + if (dosStubSize + sizeof(ImageDosHeader) > pePosition) + { + diagnostics.Error(DiagnosticId.PE_ERR_InvalidDosStubSize, $"Invalid DOS stub size {dosStubSize} going beyond the PE header"); + return; + } + + if (dosStubSize > 0) + { + var dosStub = new byte[dosStubSize]; + read = reader.Read(dosStub); + if (read != dosStubSize) + { + diagnostics.Error(DiagnosticId.PE_ERR_InvalidDosStubSize, "Invalid DOS stub"); + return; + } - // Read the PE signature - if (reader.Position != DosHeader.FileAddressPEHeader) - { - reader.Position = DosHeader.FileAddressPEHeader; + _dosStub = dosStub; + } + else + { + _dosStub = []; + } + + var dosHeaderAndStubSize = sizeof(ImageDosHeader) + dosStubSize; + + // Read any DOS stub extra data (e.g Rich) + if (dosHeaderAndStubSize < DosHeader.FileAddressPEHeader) + { + _dosStubExtra = reader.ReadAsStream((ulong)(DosHeader.FileAddressPEHeader - dosHeaderAndStubSize)); + } } + // Read the PE header + reader.Position = DosHeader.FileAddressPEHeader; + var signature = default(ImagePESignature); read = reader.Read(MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref signature, 1))); if (read != sizeof(ImagePESignature)) @@ -131,19 +152,49 @@ public override void Read(PEImageReader reader) } CoffHeader = coffHeader; - var tempArray = ArrayPool.Shared.Rent(CoffHeader.SizeOfOptionalHeader); + var positionAfterCoffHeader = reader.Position; + + // Cannot be smaller than the magic + if (CoffHeader.SizeOfOptionalHeader < sizeof(ImageOptionalHeaderMagic)) + { + diagnostics.Error(DiagnosticId.PE_ERR_InvalidOptionalHeaderSize, $"Invalid optional header size {CoffHeader.SizeOfOptionalHeader}"); + return; + } + + var magic = (ImageOptionalHeaderMagic)reader.ReadU16(); + + int minimumSizeOfOptionalHeaderToRead = 0; + + // Process known PE32/PE32+ headers + if (magic == ImageOptionalHeaderMagic.PE32) + { + minimumSizeOfOptionalHeaderToRead = RawImageOptionalHeader32.MinimumSize; + } + else if (magic == ImageOptionalHeaderMagic.PE32Plus) + { + minimumSizeOfOptionalHeaderToRead = RawImageOptionalHeader64.MinimumSize; + } + else + { + diagnostics.Error(DiagnosticId.PE_ERR_InvalidOptionalHeaderMagic, $"Invalid optional header PE magic 0x{(uint)magic:X8}"); + return; + } + + minimumSizeOfOptionalHeaderToRead = Math.Max(CoffHeader.SizeOfOptionalHeader, minimumSizeOfOptionalHeaderToRead); + + var tempArray = ArrayPool.Shared.Rent(minimumSizeOfOptionalHeaderToRead); try { - var optionalHeader = new Span(tempArray, 0, CoffHeader.SizeOfOptionalHeader); - read = reader.Read(optionalHeader); - if (read != CoffHeader.SizeOfOptionalHeader) - { - diagnostics.Error(DiagnosticId.PE_ERR_InvalidOptionalHeaderSize, "Invalid optional header"); - return; - } + var optionalHeader = new Span(tempArray, 0, minimumSizeOfOptionalHeaderToRead); + MemoryMarshal.Write(optionalHeader, (ushort)magic); - var magic = MemoryMarshal.Cast(optionalHeader.Slice(0, 2))[0]; + // We have already read the magic number + var minimumSpan = optionalHeader.Slice(sizeof(ImageOptionalHeaderMagic)); + read = reader.Read(minimumSpan); + // The real size read (in case of tricked Tiny PE) + optionalHeader = optionalHeader.Slice(0, read + sizeof(ImageOptionalHeaderMagic)); + Debug.Assert(Unsafe.SizeOf() == 224); Debug.Assert(Unsafe.SizeOf() == 240); @@ -152,9 +203,9 @@ public override void Read(PEImageReader reader) { var optionalHeader32 = new RawImageOptionalHeader32(); var span = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref optionalHeader32, 1)); - if (span.Length > CoffHeader.SizeOfOptionalHeader) + if (span.Length > optionalHeader.Length) { - span = span.Slice(0, CoffHeader.SizeOfOptionalHeader); + span = span.Slice(0, optionalHeader.Length); } optionalHeader.CopyTo(span); @@ -165,14 +216,14 @@ public override void Read(PEImageReader reader) OptionalHeader.OptionalHeaderSize32 = optionalHeader32.Size32; OptionalHeader.OptionalHeaderCommonPart3 = optionalHeader32.Common3; } - else if (magic == ImageOptionalHeaderMagic.PE32Plus) + else { var optionalHeader64 = new RawImageOptionalHeader64(); // Skip 2 bytes as we read already the magic number var span = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref optionalHeader64, 1)); - if (span.Length > CoffHeader.SizeOfOptionalHeader) + if (span.Length > optionalHeader.Length) { - span = span.Slice(0, CoffHeader.SizeOfOptionalHeader); + span = span.Slice(0, optionalHeader.Length); } optionalHeader.CopyTo(span); @@ -183,13 +234,13 @@ public override void Read(PEImageReader reader) OptionalHeader.OptionalHeaderSize64 = optionalHeader64.Size64; OptionalHeader.OptionalHeaderCommonPart3 = optionalHeader64.Common3; } - else - { - diagnostics.Error(DiagnosticId.PE_ERR_InvalidOptionalHeaderMagic, $"Invalid optional header PE magic 0x{(uint)magic:X8}"); - return; - } + + // Sets the number of entries in the data directory + Directories.Count = (int)OptionalHeader.NumberOfRvaAndSizes; // Read sections + reader.Position = positionAfterCoffHeader + CoffHeader.SizeOfOptionalHeader; + ArrayPool.Shared.Return(tempArray); Debug.Assert(Unsafe.SizeOf() == 40); var sizeOfSections = CoffHeader.NumberOfSections * Unsafe.SizeOf(); @@ -205,6 +256,9 @@ public override void Read(PEImageReader reader) // Read all sections and directories ReadSectionsAndDirectories(reader, sectionHeaders); + + // Set the size to the full size of the file + Size = reader.Length; } finally { @@ -273,11 +327,12 @@ private void ReadSectionsAndDirectories(PEImageReader reader, ReadOnlySpan positionAfterHeaders) + { + FillExtraDataWithMissingStreams(reader, this, ExtraDataBeforeSections, positionAfterHeaders, positionFirstSection - positionAfterHeaders); + } + FillExtraDataWithMissingStreams(reader, this, ExtraDataAfterSections, positionAfterLastSection, reader.Length - positionAfterLastSection); + // Create Stream data sections for remaining data per section based on directories already loaded for (var i = 0; i < _sections.Count; i++) { diff --git a/src/LibObjectFile/PE/PEFile.cs b/src/LibObjectFile/PE/PEFile.cs index 9b8f247..87f5b9b 100644 --- a/src/LibObjectFile/PE/PEFile.cs +++ b/src/LibObjectFile/PE/PEFile.cs @@ -26,16 +26,20 @@ public sealed partial class PEFile : PEObjectBase private byte[] _dosStub = []; private Stream? _dosStubExtra; private readonly ObjectList _sections; + private int _unsafeNegativePEHeaderOffset; /// /// Initializes a new instance of the class. /// - public PEFile() + public PEFile(ImageOptionalHeaderMagic magic = ImageOptionalHeaderMagic.PE32Plus) { _sections = new(this); ExtraDataBeforeSections = new(this); ExtraDataAfterSections = new(this); + // TODO: Add default initialization + + OptionalHeader.OptionalHeaderCommonPart1.Magic = magic; } /// @@ -53,6 +57,30 @@ internal PEFile(bool unused) /// public ImageDosHeader DosHeader; + /// + /// Gets or sets an unsafe negative offset relative to the end of the DOS header. + /// + public unsafe int UnsafeNegativePEHeaderOffset + { + get => _unsafeNegativePEHeaderOffset; + set + { + // Should support Tiny PE layout http://www.phreedom.org/research/tinype/ + // Value must be >= sizeof(ImageDosHeader) - 4 and <= 0 + if (value < sizeof(ImageDosHeader) - 4 || value > 0) + { + throw new ArgumentOutOfRangeException(nameof(value), $"PEHeaderOffset must be greater than {sizeof(ImageDosHeader)}"); + } + + if (value < 0 && (_dosStub.Length != 0 || (DosStubExtra is not null && DosStubExtra.Length > 0))) + { + throw new InvalidOperationException("Setting a negative PEHeader offset is not compatible with having a DOS Stub and DosStubExtra"); + } + + _unsafeNegativePEHeaderOffset = value; + } + } + /// /// Gets or sets the DOS stub. /// @@ -60,7 +88,15 @@ public byte[] DosStub { get => _dosStub; - set => _dosStub = value ?? throw new ArgumentNullException(nameof(value)); + set + { + if (_unsafeNegativePEHeaderOffset < 0) + { + throw new InvalidOperationException("Cannot set a DosStub when UnsafeNegativePEHeaderOffset is negative"); + } + + _dosStub = value ?? throw new ArgumentNullException(nameof(value)); + } } /// @@ -70,7 +106,15 @@ public Stream? DosStubExtra { get => _dosStubExtra; - set => _dosStubExtra = value; + set + { + if (_unsafeNegativePEHeaderOffset < 0) + { + throw new InvalidOperationException("Cannot set a DosStubExtra when UnsafeNegativePEHeaderOffset is negative"); + } + + _dosStubExtra = value; + } } /// @@ -278,7 +322,7 @@ public override unsafe void UpdateLayout(PELayoutContext context) position = AlignHelper.AlignUp(position, 8); // PE header is aligned on 8 bytes // Update offset to PE header - DosHeader.FileAddressPEHeader = position; + DosHeader._FileAddressPEHeader = position; position += sizeof(ImagePESignature); // PE00 header @@ -290,7 +334,7 @@ public override unsafe void UpdateLayout(PELayoutContext context) position += (uint)(IsPE32 ? sizeof(RawImageOptionalHeader32) : sizeof(RawImageOptionalHeader64)); // Update directories - position += (uint)(Directories.CalculateNumberOfEntries() * sizeof(ImageDataDirectory)); + position += (uint)(Directories.Count * sizeof(ImageDataDirectory)); // TODO: Additional optional header size? @@ -310,13 +354,13 @@ public override unsafe void UpdateLayout(PELayoutContext context) // Update COFF header - CoffHeader.NumberOfSections = (ushort)_sections.Count; - CoffHeader.PointerToSymbolTable = 0; - CoffHeader.NumberOfSymbols = 0; + CoffHeader._NumberOfSections = (ushort)_sections.Count; + CoffHeader._PointerToSymbolTable = 0; + CoffHeader._NumberOfSymbols = 0; - OptionalHeader.SizeOfCode = 0; - OptionalHeader.SizeOfInitializedData = 0; - OptionalHeader.SizeOfUninitializedData = 0; + OptionalHeader.OptionalHeaderCommonPart1.SizeOfCode = 0; + OptionalHeader.OptionalHeaderCommonPart1.SizeOfInitializedData = 0; + OptionalHeader.OptionalHeaderCommonPart1.SizeOfUninitializedData = 0; if (!BitOperations.IsPow2(OptionalHeader.FileAlignment) || OptionalHeader.FileAlignment == 0) { @@ -340,7 +384,7 @@ public override unsafe void UpdateLayout(PELayoutContext context) // Ensure that SectionAlignment is a multiple of FileAlignment position = AlignHelper.AlignUp(position, OptionalHeader.FileAlignment); - OptionalHeader.SizeOfHeaders = position; + OptionalHeader.OptionalHeaderCommonPart2.SizeOfHeaders = position; // Update sections RVA previousEndOfRVA = 0U; @@ -360,15 +404,15 @@ public override unsafe void UpdateLayout(PELayoutContext context) if ((section.Characteristics & SectionCharacteristics.ContainsCode) != 0) { - OptionalHeader.SizeOfCode += virtualSizeDiskAligned; + OptionalHeader.OptionalHeaderCommonPart1.SizeOfCode += virtualSizeDiskAligned; } else if ((section.Characteristics & SectionCharacteristics.ContainsInitializedData) != 0) { - OptionalHeader.SizeOfInitializedData += virtualSizeDiskAligned; + OptionalHeader.OptionalHeaderCommonPart1.SizeOfInitializedData += virtualSizeDiskAligned; } else if ((section.Characteristics & SectionCharacteristics.ContainsUninitializedData) != 0) { - OptionalHeader.SizeOfUninitializedData += virtualSizeDiskAligned; + OptionalHeader.OptionalHeaderCommonPart1.SizeOfUninitializedData += virtualSizeDiskAligned; } // Update the end of the RVA @@ -376,7 +420,7 @@ public override unsafe void UpdateLayout(PELayoutContext context) } // Update the (virtual) size of the image - OptionalHeader.SizeOfImage = previousEndOfRVA; + OptionalHeader.OptionalHeaderCommonPart2.SizeOfImage = previousEndOfRVA; // Data after sections foreach (var extraData in ExtraDataAfterSections) diff --git a/src/LibObjectFile/PE/PEPrinter.cs b/src/LibObjectFile/PE/PEPrinter.cs index f8f4cde..871e43e 100644 --- a/src/LibObjectFile/PE/PEPrinter.cs +++ b/src/LibObjectFile/PE/PEPrinter.cs @@ -136,9 +136,11 @@ private static void PrintOptionalHeader(PEFile file, ref TextWriterIndenter writ private static void PrintDataDirectories(PEFile file, ref TextWriterIndenter writer) { + if (file.Directories.Count == 0) return; + writer.WriteLine("Data Directories"); writer.Indent(); - for(int i = (int)PEDataDirectoryKind.Export; i <= (int)PEDataDirectoryKind.ClrMetadata; i++) + for(int i = 0; i < file.Directories.Count; i++) { var kind = (PEDataDirectoryKind)i; var directory = file.Directories[kind];