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;
}
}