using System.IO; using BurnOutSharp.Models.PortableExecutable; namespace BurnOutSharp.Builder { // TODO: Make Stream Data rely on Byte Data public static class PortableExecutable { #region Byte Data /// /// Parse a byte array into a Portable Executable /// /// Byte array to parse /// Offset into the byte array /// Filled executable on success, null on error public static Executable ParseExecutable(byte[] data, int offset) { // If the data is invalid if (data == null) return null; // If the offset is out of bounds if (offset < 0 || offset >= data.Length) return null; // Cache the current offset int initialOffset = offset; // Create a new executable to fill var executable = new Executable(); #region MS-DOS Stub // Parse the MS-DOS stub var stub = MSDOS.ParseExecutable(data, offset); if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0) return null; // Set the MS-DOS stub executable.Stub = stub; #endregion #region Signature offset = (int)(initialOffset + stub.Header.NewExeHeaderAddr); executable.Signature = new byte[4]; for (int i = 0; i < executable.Signature.Length; i++) { executable.Signature[i] = data.ReadByte(ref offset); } if (executable.Signature[0] != 'P' || executable.Signature[1] != 'E' || executable.Signature[2] != '\0' || executable.Signature[3] != '\0') return null; #endregion #region COFF File Header // Try to parse the COFF file header var coffFileHeader = ParseCOFFFileHeader(data, ref offset); if (coffFileHeader == null) return null; // Set the COFF file header executable.COFFFileHeader = coffFileHeader; #endregion #region Optional Header // Try to parse the optional header var optionalHeader = ParseOptionalHeader(data, offset, coffFileHeader.SizeOfOptionalHeader); if (optionalHeader == null) return null; // Set the optional header executable.OptionalHeader = optionalHeader; #endregion // TODO: Finish implementing PE parsing return executable; } /// /// Parse a byte array into a Portable Executable COFF file header /// /// Byte array to parse /// Offset into the byte array /// Filled COFF file header on success, null on error private static COFFFileHeader ParseCOFFFileHeader(byte[] data, ref int offset) { // TODO: Use marshalling here instead of building var fileHeader = new COFFFileHeader(); fileHeader.Machine = (MachineType)data.ReadUInt16(ref offset); fileHeader.NumberOfSections = data.ReadUInt16(ref offset); fileHeader.TimeDateStamp = data.ReadUInt32(ref offset); fileHeader.PointerToSymbolTable = data.ReadUInt32(ref offset); fileHeader.NumberOfSymbols = data.ReadUInt32(ref offset); fileHeader.SizeOfOptionalHeader = data.ReadUInt16(ref offset); fileHeader.Characteristics = (Characteristics)data.ReadUInt16(ref offset); return fileHeader; } /// /// Parse a byte array into an optional header /// /// Byte array to parse /// Offset into the byte array /// Size of the optional header /// Filled optional header on success, null on error private static OptionalHeader ParseOptionalHeader(byte[] data, int offset, int optionalSize) { int initialOffset = offset; // TODO: Use marshalling here instead of building var optionalHeader = new OptionalHeader(); #region Standard Fields optionalHeader.Magic = (OptionalHeaderMagicNumber)data.ReadUInt16(ref offset); optionalHeader.MajorLinkerVersion = data.ReadByte(ref offset); optionalHeader.MinorLinkerVersion = data.ReadByte(ref offset); optionalHeader.SizeOfCode = data.ReadUInt32(ref offset); optionalHeader.SizeOfInitializedData = data.ReadUInt32(ref offset); optionalHeader.SizeOfUninitializedData = data.ReadUInt32(ref offset); optionalHeader.AddressOfEntryPoint = data.ReadUInt32(ref offset); optionalHeader.BaseOfCode = data.ReadUInt32(ref offset); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.BaseOfData = data.ReadUInt32(ref offset); #endregion #region Windows-Specific Fields if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.ImageBase_PE32 = data.ReadUInt32(ref offset); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.ImageBase_PE32Plus = data.ReadUInt64(ref offset); optionalHeader.FileAlignment = data.ReadUInt32(ref offset); optionalHeader.MajorOperatingSystemVersion = data.ReadUInt16(ref offset); optionalHeader.MinorOperatingSystemVersion = data.ReadUInt16(ref offset); optionalHeader.MajorImageVersion = data.ReadUInt16(ref offset); optionalHeader.MinorImageVersion = data.ReadUInt16(ref offset); optionalHeader.MajorSubsystemVersion = data.ReadUInt16(ref offset); optionalHeader.MinorSubsystemVersion = data.ReadUInt16(ref offset); optionalHeader.Win32VersionValue = data.ReadUInt32(ref offset); optionalHeader.SizeOfImage = data.ReadUInt32(ref offset); optionalHeader.SizeOfHeaders = data.ReadUInt32(ref offset); optionalHeader.CheckSum = data.ReadUInt32(ref offset); optionalHeader.Subsystem = (WindowsSubsystem)data.ReadUInt16(ref offset); optionalHeader.DllCharacteristics = (DllCharacteristics)data.ReadUInt16(ref offset); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.SizeOfStackReserve_PE32 = data.ReadUInt32(ref offset); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.SizeOfStackReserve_PE32Plus = data.ReadUInt64(ref offset); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.SizeOfStackCommit_PE32 = data.ReadUInt32(ref offset); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.SizeOfStackCommit_PE32Plus = data.ReadUInt64(ref offset); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.SizeOfHeapReserve_PE32 = data.ReadUInt32(ref offset); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.SizeOfHeapReserve_PE32Plus = data.ReadUInt64(ref offset); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.SizeOfHeapCommit_PE32 = data.ReadUInt32(ref offset); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.SizeOfHeapCommit_PE32Plus = data.ReadUInt64(ref offset); optionalHeader.LoaderFlags = data.ReadUInt32(ref offset); optionalHeader.NumberOfRvaAndSizes = data.ReadUInt32(ref offset); #endregion #region Data Directories if (optionalHeader.NumberOfRvaAndSizes >= 1 && offset - initialOffset < optionalSize) { optionalHeader.ExportTable = new DataDirectory(); optionalHeader.ExportTable.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.ExportTable.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 2 && offset - initialOffset < optionalSize) { optionalHeader.ImportTable = new DataDirectory(); optionalHeader.ImportTable.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.ImportTable.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 3 && offset - initialOffset < optionalSize) { optionalHeader.ResourceTable = new DataDirectory(); optionalHeader.ResourceTable.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.ResourceTable.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 4 && offset - initialOffset < optionalSize) { optionalHeader.ExceptionTable = new DataDirectory(); optionalHeader.ExceptionTable.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.ExceptionTable.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 5 && offset - initialOffset < optionalSize) { optionalHeader.CertificateTable = new DataDirectory(); optionalHeader.CertificateTable.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.CertificateTable.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 6 && offset - initialOffset < optionalSize) { optionalHeader.BaseRelocationTable = new DataDirectory(); optionalHeader.BaseRelocationTable.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.BaseRelocationTable.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 7 && offset - initialOffset < optionalSize) { optionalHeader.Debug = new DataDirectory(); optionalHeader.Debug.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.Debug.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 8 && offset - initialOffset < optionalSize) { optionalHeader.Architecture = data.ReadUInt64(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 9 && offset - initialOffset < optionalSize) { optionalHeader.GlobalPtr = new DataDirectory(); optionalHeader.GlobalPtr.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.GlobalPtr.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 10 && offset - initialOffset < optionalSize) { optionalHeader.ThreadLocalStorageTable = new DataDirectory(); optionalHeader.ThreadLocalStorageTable.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.ThreadLocalStorageTable.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 11 && offset - initialOffset < optionalSize) { optionalHeader.LoadConfigTable = new DataDirectory(); optionalHeader.LoadConfigTable.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.LoadConfigTable.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 12 && offset - initialOffset < optionalSize) { optionalHeader.BoundImport = new DataDirectory(); optionalHeader.BoundImport.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.BoundImport.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 13 && offset - initialOffset < optionalSize) { optionalHeader.ImportAddressTable = new DataDirectory(); optionalHeader.ImportAddressTable.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.ImportAddressTable.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 14 && offset - initialOffset < optionalSize) { optionalHeader.DelayImportDescriptor = new DataDirectory(); optionalHeader.DelayImportDescriptor.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.DelayImportDescriptor.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 15 && offset - initialOffset < optionalSize) { optionalHeader.CLRRuntimeHeader = new DataDirectory(); optionalHeader.CLRRuntimeHeader.VirtualAddress = data.ReadUInt32(ref offset); optionalHeader.CLRRuntimeHeader.Size = data.ReadUInt32(ref offset); } if (optionalHeader.NumberOfRvaAndSizes >= 16 && offset - initialOffset < optionalSize) { optionalHeader.Reserved = data.ReadUInt64(ref offset); } #endregion return optionalHeader; } #endregion #region Stream Data /// /// Parse a Stream into a Portable Executable /// /// Stream to parse /// Filled executable on success, null on error public static Executable ParseExecutable(Stream data) { // If the data is invalid if (data == null) return null; // If the offset is out of bounds if (data.Position < 0 || data.Position >= data.Length) return null; // Cache the current offset int initialOffset = (int)data.Position; // Create a new executable to fill var executable = new Executable(); #region MS-DOS Stub // Parse the MS-DOS stub var stub = MSDOS.ParseExecutable(data); if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0) return null; // Set the MS-DOS stub executable.Stub = stub; #endregion #region Signature data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin); executable.Signature = new byte[4]; for (int i = 0; i < executable.Signature.Length; i++) { executable.Signature[i] = data.ReadByteValue(); } if (executable.Signature[0] != 'P' || executable.Signature[1] != 'E' || executable.Signature[2] != '\0' || executable.Signature[3] != '\0') return null; #endregion #region COFF File Header // Try to parse the COFF file header var coffFileHeader = ParseCOFFFileHeader(data); if (coffFileHeader == null) return null; // Set the COFF file header executable.COFFFileHeader = coffFileHeader; #endregion #region Optional Header // Try to parse the optional header var optionalHeader = ParseOptionalHeader(data, coffFileHeader.SizeOfOptionalHeader); if (optionalHeader == null) return null; // Set the optional header executable.OptionalHeader = optionalHeader; #endregion // TODO: Finish implementing PE parsing return executable; } /// /// Parse a Stream into a Portable Executable COFF file header /// /// Stream to parse /// Filled executable header on success, null on error private static COFFFileHeader ParseCOFFFileHeader(Stream data) { // TODO: Use marshalling here instead of building var fileHeader = new COFFFileHeader(); fileHeader.Machine = (MachineType)data.ReadUInt16(); fileHeader.NumberOfSections = data.ReadUInt16(); fileHeader.TimeDateStamp = data.ReadUInt32(); fileHeader.PointerToSymbolTable = data.ReadUInt32(); fileHeader.NumberOfSymbols = data.ReadUInt32(); fileHeader.SizeOfOptionalHeader = data.ReadUInt16(); fileHeader.Characteristics = (Characteristics)data.ReadUInt16(); return fileHeader; } /// /// Parse a Stream into an optional header /// /// Stream to parse /// Size of the optional header /// Filled optional header on success, null on error private static OptionalHeader ParseOptionalHeader(Stream data, int optionalSize) { long initialOffset = data.Position; // TODO: Use marshalling here instead of building var optionalHeader = new OptionalHeader(); #region Standard Fields optionalHeader.Magic = (OptionalHeaderMagicNumber)data.ReadUInt16(); optionalHeader.MajorLinkerVersion = data.ReadByteValue(); optionalHeader.MinorLinkerVersion = data.ReadByteValue(); optionalHeader.SizeOfCode = data.ReadUInt32(); optionalHeader.SizeOfInitializedData = data.ReadUInt32(); optionalHeader.SizeOfUninitializedData = data.ReadUInt32(); optionalHeader.AddressOfEntryPoint = data.ReadUInt32(); optionalHeader.BaseOfCode = data.ReadUInt32(); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.BaseOfData = data.ReadUInt32(); #endregion #region Windows-Specific Fields if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.ImageBase_PE32 = data.ReadUInt32(); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.ImageBase_PE32Plus = data.ReadUInt64(); optionalHeader.FileAlignment = data.ReadUInt32(); optionalHeader.MajorOperatingSystemVersion = data.ReadUInt16(); optionalHeader.MinorOperatingSystemVersion = data.ReadUInt16(); optionalHeader.MajorImageVersion = data.ReadUInt16(); optionalHeader.MinorImageVersion = data.ReadUInt16(); optionalHeader.MajorSubsystemVersion = data.ReadUInt16(); optionalHeader.MinorSubsystemVersion = data.ReadUInt16(); optionalHeader.Win32VersionValue = data.ReadUInt32(); optionalHeader.SizeOfImage = data.ReadUInt32(); optionalHeader.SizeOfHeaders = data.ReadUInt32(); optionalHeader.CheckSum = data.ReadUInt32(); optionalHeader.Subsystem = (WindowsSubsystem)data.ReadUInt16(); optionalHeader.DllCharacteristics = (DllCharacteristics)data.ReadUInt16(); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.SizeOfStackReserve_PE32 = data.ReadUInt32(); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.SizeOfStackReserve_PE32Plus = data.ReadUInt64(); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.SizeOfStackCommit_PE32 = data.ReadUInt32(); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.SizeOfStackCommit_PE32Plus = data.ReadUInt64(); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.SizeOfHeapReserve_PE32 = data.ReadUInt32(); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.SizeOfHeapReserve_PE32Plus = data.ReadUInt64(); if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) optionalHeader.SizeOfHeapCommit_PE32 = data.ReadUInt32(); else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) optionalHeader.SizeOfHeapCommit_PE32Plus = data.ReadUInt64(); optionalHeader.LoaderFlags = data.ReadUInt32(); optionalHeader.NumberOfRvaAndSizes = data.ReadUInt32(); #endregion #region Data Directories if (optionalHeader.NumberOfRvaAndSizes >= 1 && data.Position - initialOffset < optionalSize) { optionalHeader.ExportTable = new DataDirectory(); optionalHeader.ExportTable.VirtualAddress = data.ReadUInt32(); optionalHeader.ExportTable.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 2 && data.Position - initialOffset < optionalSize) { optionalHeader.ImportTable = new DataDirectory(); optionalHeader.ImportTable.VirtualAddress = data.ReadUInt32(); optionalHeader.ImportTable.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 3 && data.Position - initialOffset < optionalSize) { optionalHeader.ResourceTable = new DataDirectory(); optionalHeader.ResourceTable.VirtualAddress = data.ReadUInt32(); optionalHeader.ResourceTable.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 4 && data.Position - initialOffset < optionalSize) { optionalHeader.ExceptionTable = new DataDirectory(); optionalHeader.ExceptionTable.VirtualAddress = data.ReadUInt32(); optionalHeader.ExceptionTable.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 5 && data.Position - initialOffset < optionalSize) { optionalHeader.CertificateTable = new DataDirectory(); optionalHeader.CertificateTable.VirtualAddress = data.ReadUInt32(); optionalHeader.CertificateTable.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 6 && data.Position - initialOffset < optionalSize) { optionalHeader.BaseRelocationTable = new DataDirectory(); optionalHeader.BaseRelocationTable.VirtualAddress = data.ReadUInt32(); optionalHeader.BaseRelocationTable.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 7 && data.Position - initialOffset < optionalSize) { optionalHeader.Debug = new DataDirectory(); optionalHeader.Debug.VirtualAddress = data.ReadUInt32(); optionalHeader.Debug.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 8 && data.Position - initialOffset < optionalSize) { optionalHeader.Architecture = data.ReadUInt64(); } if (optionalHeader.NumberOfRvaAndSizes >= 9 && data.Position - initialOffset < optionalSize) { optionalHeader.GlobalPtr = new DataDirectory(); optionalHeader.GlobalPtr.VirtualAddress = data.ReadUInt32(); optionalHeader.GlobalPtr.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 10 && data.Position - initialOffset < optionalSize) { optionalHeader.ThreadLocalStorageTable = new DataDirectory(); optionalHeader.ThreadLocalStorageTable.VirtualAddress = data.ReadUInt32(); optionalHeader.ThreadLocalStorageTable.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 11 && data.Position - initialOffset < optionalSize) { optionalHeader.LoadConfigTable = new DataDirectory(); optionalHeader.LoadConfigTable.VirtualAddress = data.ReadUInt32(); optionalHeader.LoadConfigTable.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 12 && data.Position - initialOffset < optionalSize) { optionalHeader.BoundImport = new DataDirectory(); optionalHeader.BoundImport.VirtualAddress = data.ReadUInt32(); optionalHeader.BoundImport.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 13 && data.Position - initialOffset < optionalSize) { optionalHeader.ImportAddressTable = new DataDirectory(); optionalHeader.ImportAddressTable.VirtualAddress = data.ReadUInt32(); optionalHeader.ImportAddressTable.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 14 && data.Position - initialOffset < optionalSize) { optionalHeader.DelayImportDescriptor = new DataDirectory(); optionalHeader.DelayImportDescriptor.VirtualAddress = data.ReadUInt32(); optionalHeader.DelayImportDescriptor.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 15 && data.Position - initialOffset < optionalSize) { optionalHeader.CLRRuntimeHeader = new DataDirectory(); optionalHeader.CLRRuntimeHeader.VirtualAddress = data.ReadUInt32(); optionalHeader.CLRRuntimeHeader.Size = data.ReadUInt32(); } if (optionalHeader.NumberOfRvaAndSizes >= 16 && data.Position - initialOffset < optionalSize) { optionalHeader.Reserved = data.ReadUInt64(); } #endregion return optionalHeader; } #endregion } }