using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace QuickLook.Plugin.PEViewer.PEImageParser; /// /// Represents a PE (x86) or a PE+ (x64) image. This class parses binary files, typically EXE and DLL files. /// public class PEImage { /// /// Gets the original PE image file, if this file was loaded from an existing source; otherwise, . /// public byte[] OriginalImage { get; private set; } /// /// Gets the DOS header of this PE image file. /// public ImageDosHeader DosHeader { get; private set; } /// /// Gets the MS-DOS stub of this PE image file. /// public byte[] DosStub { get; private set; } /// /// Gets the COFF header of this PE image file. /// public ImageCoffHeader CoffHeader { get; private set; } /// /// Gets the optional header of this PE image file. /// public ImageOptionalHeader OptionalHeader { get; private set; } /// /// Gets the collection of section headers and data of this PE image file. /// public ImageSection[] Sections { get; private set; } private PEImage(byte[] originalImage) { OriginalImage = originalImage; using BinaryReader reader = new(new MemoryStream(OriginalImage)); // MZ if (reader.BaseStream.Length < 2) throw new PEImageParseException(0, "DOS signature not found."); if (reader.ReadUInt16() != 0x5a4d) throw new PEImageParseException(0, "DOS header not found."); // DOS Header if (reader.BaseStream.Length - reader.BaseStream.Position < 64) throw new PEImageParseException((int)reader.BaseStream.Position, "DOS header incomplete."); DosHeader = new() { LastPageSize = reader.ReadUInt16(), PageCount = reader.ReadUInt16(), RelocationCount = reader.ReadUInt16(), HeaderSize = reader.ReadUInt16(), MinAlloc = reader.ReadUInt16(), MaxAlloc = reader.ReadUInt16(), InitialSS = reader.ReadUInt16(), InitialSP = reader.ReadUInt16(), Checksum = reader.ReadUInt16(), InitialIP = reader.ReadUInt16(), InitialCS = reader.ReadUInt16(), RelocationOffset = reader.ReadUInt16(), OverlayNumber = reader.ReadUInt16(), Reserved1 = reader.ReadUInt16(), Reserved2 = reader.ReadUInt16(), Reserved3 = reader.ReadUInt16(), Reserved4 = reader.ReadUInt16(), OemIdentifier = reader.ReadUInt16(), OemInformation = reader.ReadUInt16(), Reserved5 = reader.ReadUInt16(), Reserved6 = reader.ReadUInt16(), Reserved7 = reader.ReadUInt16(), Reserved8 = reader.ReadUInt16(), Reserved9 = reader.ReadUInt16(), Reserved10 = reader.ReadUInt16(), Reserved11 = reader.ReadUInt16(), Reserved12 = reader.ReadUInt16(), Reserved13 = reader.ReadUInt16(), Reserved14 = reader.ReadUInt16(), PEHeaderOffset = reader.ReadUInt32() }; // DOS Stub if (reader.BaseStream.Length < DosHeader.PEHeaderOffset) throw new PEImageParseException((int)reader.BaseStream.Position, "DOS stub incomplete."); DosStub = reader.ReadBytes((int)(DosHeader.PEHeaderOffset - reader.BaseStream.Position)); // COFF Header if (reader.ReadUInt32() != 0x4550) throw new PEImageParseException((int)reader.BaseStream.Position - 4, "COFF header not found."); if (reader.BaseStream.Length - reader.BaseStream.Position < 20) throw new PEImageParseException((int)reader.BaseStream.Position, "COFF header incomplete."); CoffHeader = new() { Machine = (ImageMachineType)reader.ReadUInt16(), NumberOfSections = reader.ReadUInt16(), TimeDateStamp = reader.ReadUInt32(), PointerToSymbolTable = reader.ReadUInt32(), NumberOfSymbols = reader.ReadUInt32(), SizeOfOptionalHeader = reader.ReadUInt16(), Characteristics = (ImageCharacteristics)reader.ReadUInt16() }; // Optional Header if (reader.BaseStream.Length - reader.BaseStream.Position < 2) throw new PEImageParseException((int)reader.BaseStream.Position, "Optional header not found."); ushort magic = reader.ReadUInt16(); if (magic == 0x10b) { if (reader.BaseStream.Length - reader.BaseStream.Position < 94) throw new PEImageParseException((int)reader.BaseStream.Position, "Optional header incomplete."); OptionalHeader = new ImageOptionalHeader32 { MajorLinkerVersion = reader.ReadByte(), MinorLinkerVersion = reader.ReadByte(), SizeOfCode = reader.ReadUInt32(), SizeOfInitializedData = reader.ReadUInt32(), SizeOfUninitializedData = reader.ReadUInt32(), AddressOfEntryPoint = reader.ReadUInt32(), BaseOfCode = reader.ReadUInt32(), BaseOfData = reader.ReadUInt32(), ImageBase = reader.ReadUInt32(), SectionAlignment = reader.ReadUInt32(), FileAlignment = reader.ReadUInt32(), MajorOperatingSystemVersion = reader.ReadUInt16(), MinorOperatingSystemVersion = reader.ReadUInt16(), MajorImageVersion = reader.ReadUInt16(), MinorImageVersion = reader.ReadUInt16(), MajorSubsystemVersion = reader.ReadUInt16(), MinorSubsystemVersion = reader.ReadUInt16(), Win32VersionValue = reader.ReadUInt32(), SizeOfImage = reader.ReadUInt32(), SizeOfHeaders = reader.ReadUInt32(), Checksum = reader.ReadUInt32(), Subsystem = (ImageSubsystem)reader.ReadUInt16(), DllCharacteristics = (ImageDllCharacteristics)reader.ReadUInt16(), SizeOfStackReserve = reader.ReadUInt32(), SizeOfStackCommit = reader.ReadUInt32(), SizeOfHeapReserve = reader.ReadUInt32(), SizeOfHeapCommit = reader.ReadUInt32(), LoaderFlags = reader.ReadUInt32(), NumberOfRvaAndSizes = reader.ReadUInt32() }; } else if (magic == 0x20b) { if (reader.BaseStream.Length - reader.BaseStream.Position < 110) throw new PEImageParseException((int)reader.BaseStream.Position, "Optional header incomplete."); OptionalHeader = new ImageOptionalHeader64 { MajorLinkerVersion = reader.ReadByte(), MinorLinkerVersion = reader.ReadByte(), SizeOfCode = reader.ReadUInt32(), SizeOfInitializedData = reader.ReadUInt32(), SizeOfUninitializedData = reader.ReadUInt32(), AddressOfEntryPoint = reader.ReadUInt32(), BaseOfCode = reader.ReadUInt32(), ImageBase = reader.ReadUInt64(), SectionAlignment = reader.ReadUInt32(), FileAlignment = reader.ReadUInt32(), MajorOperatingSystemVersion = reader.ReadUInt16(), MinorOperatingSystemVersion = reader.ReadUInt16(), MajorImageVersion = reader.ReadUInt16(), MinorImageVersion = reader.ReadUInt16(), MajorSubsystemVersion = reader.ReadUInt16(), MinorSubsystemVersion = reader.ReadUInt16(), Win32VersionValue = reader.ReadUInt32(), SizeOfImage = reader.ReadUInt32(), SizeOfHeaders = reader.ReadUInt32(), Checksum = reader.ReadUInt32(), Subsystem = (ImageSubsystem)reader.ReadUInt16(), DllCharacteristics = (ImageDllCharacteristics)reader.ReadUInt16(), SizeOfStackReserve = reader.ReadUInt64(), SizeOfStackCommit = reader.ReadUInt64(), SizeOfHeapReserve = reader.ReadUInt64(), SizeOfHeapCommit = reader.ReadUInt64(), LoaderFlags = reader.ReadUInt32(), NumberOfRvaAndSizes = reader.ReadUInt32() }; } else if (magic == 0x107) { throw new PEImageParseException((int)reader.BaseStream.Position - 2, "Optional header for ROM's is not supported."); } else { throw new PEImageParseException((int)reader.BaseStream.Position - 2, "Optional header magic value of '0x" + magic.ToString("x4") + "' unknown."); } // Data Directories if (reader.BaseStream.Length - reader.BaseStream.Position < OptionalHeader.NumberOfRvaAndSizes * 8) throw new PEImageParseException((int)reader.BaseStream.Position, "Data directories incomplete."); OptionalHeader.DataDirectories = Create.Array((int)OptionalHeader.NumberOfRvaAndSizes, i => new ImageDataDirectory((ImageDataDirectoryName)i, reader.ReadUInt32(), reader.ReadUInt32())); // Section Headers if (reader.BaseStream.Length - reader.BaseStream.Position < CoffHeader.NumberOfSections * 40) throw new PEImageParseException((int)reader.BaseStream.Position, "Section headers incomplete."); Sections = Create .Enumerable(CoffHeader.NumberOfSections, i => new ImageSectionHeader { Name = reader.ReadBytes(8).TakeWhile(c => c != 0).ToArray().ToUTF8String(), VirtualSize = reader.ReadUInt32(), VirtualAddress = reader.ReadUInt32(), SizeOfRawData = reader.ReadUInt32(), PointerToRawData = reader.ReadUInt32(), PointerToRelocations = reader.ReadUInt32(), PointerToLineNumbers = reader.ReadUInt32(), NumberOfRelocations = reader.ReadUInt16(), NumberOfLineNumbers = reader.ReadUInt16(), Characteristics = (ImageSectionFlags)reader.ReadUInt32() }) .Select(header => { return new ImageSection(header); //if (header.PointerToRawData + header.SizeOfRawData <= reader.BaseStream.Length) //{ // return new ImageSection(header, OriginalImage.GetBytes((int)header.PointerToRawData, (int)header.SizeOfRawData)); //} //else //{ // throw new PEImageParseException((int)reader.BaseStream.Position, "Section '" + header.Name + "' incomplete."); //} }) .ToArray(); } /// /// Creates a from the specified file with the specified form name. /// /// A specifying the path of a file from which to create the . /// /// The this method creates. /// public static PEImage FromFile(string path) { _ = path ?? throw new ArgumentNullException(nameof(path)); _ = File.Exists(path) ? default(bool) : throw new FileNotFoundException(path); return new(File.ReadAllBytes(path)); } /// /// Creates a from the specified [] that represents a PE image file. /// /// The [] that represents a to parse. /// /// The this method creates. /// public static PEImage FromBinary(byte[] file) { _ = file ?? throw new ArgumentNullException(nameof(file)); return new([.. file]); } } /// /// Provides support for creation and generation of generic objects. /// file static class Create { /// /// Creates an of the specified type and initialized each element with a value that is retrieved from . /// /// The type of the created . /// The number of elements of the . /// A to retrieve new values for the based on the given index. /// /// A new with the specified length, where each element is initialized with a value that is retrieved from . /// public static T[] Array(int length, Func valueSelector) { T[] array = new T[length]; for (int i = 0; i < length; i++) { array[i] = valueSelector(i); } return array; } /// /// Creates an of the specified type and returns values that are retrieved from . /// /// The type of the created . /// The number of elements to return. /// A to retrieve new values for the based on the given index. /// /// A new with the specified number of elements, where each element is initialized with a value that is retrieved from . /// public static IEnumerable Enumerable(int count, Func valueSelector) { for (int i = 0; i < count; i++) { yield return valueSelector(i); } } /// /// Decodes all the bytes in this [] into a using the encoding. /// /// The [] containing the sequence of bytes to decode. /// /// A that contains the results of decoding this sequence of bytes. /// public static string ToUTF8String(this byte[] array) { return Encoding.UTF8.GetString(array); } /// /// Copies a specified number of bytes from this [] and returns a new array representing a fraction of the original []. /// /// The [] to take the subset of bytes from. /// A value specifying the offset from which to start copying bytes. /// A value specifying the number of bytes to copy. /// /// A new [] representing a fraction of the original []. /// public static byte[] GetBytes(this byte[] array, int index, int count) { byte[] result = new byte[count]; Buffer.BlockCopy(array, index, result, 0, count); return result; } }