From ec83669d7db840cc826c57a63f15049f27a6b87d Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 8 Mar 2022 23:03:26 -0800 Subject: [PATCH] Create Executable constructors --- .../ExecutableType/Microsoft/NewExecutable.cs | 108 +++-- .../Microsoft/PortableExecutable.cs | 406 ++++++++++-------- BurnOutSharp/FileType/Executable.cs | 8 +- BurnOutSharp/Tools/Utilities.cs | 2 +- 4 files changed, 316 insertions(+), 208 deletions(-) diff --git a/BurnOutSharp/ExecutableType/Microsoft/NewExecutable.cs b/BurnOutSharp/ExecutableType/Microsoft/NewExecutable.cs index 33c6d5d2..99923831 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/NewExecutable.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/NewExecutable.cs @@ -10,6 +10,21 @@ namespace BurnOutSharp.ExecutableType.Microsoft /// public class NewExecutable { + /// + /// Value determining if the executable is initialized or not + /// + public bool Initialized { get; } = false; + + /// + /// Source array that the executable was parsed from + /// + public byte[] SourceArray { get; } = null; + + /// + /// Source stream that the executable was parsed from + /// + public Stream SourceStream { get; } = null; + #region Headers /// @@ -28,70 +43,109 @@ namespace BurnOutSharp.ExecutableType.Microsoft #endregion + #region Constructors + // TODO: Add more and more parts of a standard NE executable, not just the header // TODO: Tables? What about the tables? // TODO: Implement the rest of the structures found at http://bytepointer.com/resources/win16_ne_exe_format_win3.0.htm // (Left off at RESIDENT-NAME TABLE) - public static NewExecutable Deserialize(Stream stream) + /// + /// Create a NewExecutable object from a stream + /// + /// Stream representing a file + /// + /// This constructor assumes that the stream is already in the correct position to start parsing + /// + public NewExecutable(Stream stream) { - NewExecutable nex = new NewExecutable(); + if (stream == null || !stream.CanRead || !stream.CanSeek) + return; + this.Initialized = Deserialize(stream); + this.SourceStream = stream; + } + + /// + /// Create a NewExecutable object from a byte array + /// + /// Byte array representing a file + /// Positive offset representing the current position in the array + public NewExecutable(byte[] fileContent, int offset) + { + if (fileContent == null || fileContent.Length == 0 || offset < 0) + return; + + this.Initialized = Deserialize(fileContent, offset); + this.SourceArray = fileContent; + } + + /// + /// Deserialize a NewExecutable object from a stream + /// + /// Stream representing a file + private bool Deserialize(Stream stream) + { try { // Attempt to read the DOS header first - nex.DOSStubHeader = MSDOSExecutableHeader.Deserialize(stream); - stream.Seek(nex.DOSStubHeader.NewExeHeaderAddr, SeekOrigin.Begin); - if (nex.DOSStubHeader.Magic != Constants.IMAGE_DOS_SIGNATURE) - return null; + this.DOSStubHeader = MSDOSExecutableHeader.Deserialize(stream); + stream.Seek(this.DOSStubHeader.NewExeHeaderAddr, SeekOrigin.Begin); + if (this.DOSStubHeader.Magic != Constants.IMAGE_DOS_SIGNATURE) + return false; // If the new header address is invalid for the file, it's not a NE - if (nex.DOSStubHeader.NewExeHeaderAddr >= stream.Length) - return null; + if (this.DOSStubHeader.NewExeHeaderAddr >= stream.Length) + return false; // Then attempt to read the NE header - nex.NewExecutableHeader = NewExecutableHeader.Deserialize(stream); - if (nex.NewExecutableHeader.Magic != Constants.IMAGE_OS2_SIGNATURE) - return null; + this.NewExecutableHeader = NewExecutableHeader.Deserialize(stream); + if (this.NewExecutableHeader.Magic != Constants.IMAGE_OS2_SIGNATURE) + return false; } catch (Exception ex) { //Console.WriteLine($"Errored out on a file: {ex}"); - return null; + return false; } - return nex; + return true; } - public static NewExecutable Deserialize(byte[] content, int offset) + /// + /// Deserialize a NewExecutable object from a byte array + /// + /// Byte array representing a file + /// Positive offset representing the current position in the array + private bool Deserialize(byte[] content, int offset) { - NewExecutable nex = new NewExecutable(); - try { // Attempt to read the DOS header first - nex.DOSStubHeader = MSDOSExecutableHeader.Deserialize(content, ref offset); - offset = nex.DOSStubHeader.NewExeHeaderAddr; - if (nex.DOSStubHeader.Magic != Constants.IMAGE_DOS_SIGNATURE) - return null; + this.DOSStubHeader = MSDOSExecutableHeader.Deserialize(content, ref offset); + offset = this.DOSStubHeader.NewExeHeaderAddr; + if (this.DOSStubHeader.Magic != Constants.IMAGE_DOS_SIGNATURE) + return false; // If the new header address is invalid for the file, it's not a PE - if (nex.DOSStubHeader.NewExeHeaderAddr >= content.Length) - return null; + if (this.DOSStubHeader.NewExeHeaderAddr >= content.Length) + return false; // Then attempt to read the NE header - nex.NewExecutableHeader = NewExecutableHeader.Deserialize(content, ref offset); - if (nex.NewExecutableHeader.Magic != Constants.IMAGE_OS2_SIGNATURE) - return null; + this.NewExecutableHeader = NewExecutableHeader.Deserialize(content, ref offset); + if (this.NewExecutableHeader.Magic != Constants.IMAGE_OS2_SIGNATURE) + return false; } catch (Exception ex) { //Console.WriteLine($"Errored out on a file: {ex}"); - return null; + return false; } - return nex; + return true; } + + #endregion } } \ No newline at end of file diff --git a/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs b/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs index 715207c2..5ec199d4 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs @@ -15,6 +15,21 @@ namespace BurnOutSharp.ExecutableType.Microsoft /// public class PortableExecutable { + /// + /// Value determining if the executable is initialized or not + /// + public bool Initialized { get; } = false; + + /// + /// Source array that the executable was parsed from + /// + public byte[] SourceArray { get; } = null; + + /// + /// Source stream that the executable was parsed from + /// + public Stream SourceStream { get; } = null; + #region Headers /// @@ -25,7 +40,7 @@ namespace BurnOutSharp.ExecutableType.Microsoft /// At location 0x3c, the stub has the file offset to the PE signature. /// This information enables Windows to properly execute the image file, even though it has an MS-DOS stub. /// This file offset is placed at location 0x3c during linking. - // + /// public MSDOSExecutableHeader DOSStubHeader; /// @@ -152,6 +167,220 @@ namespace BurnOutSharp.ExecutableType.Microsoft #endregion + #region Constructors + + /// + /// Create a PortableExecutable object from a stream + /// + /// Stream representing a file + /// + /// This constructor assumes that the stream is already in the correct position to start parsing + /// + public PortableExecutable(Stream stream) + { + if (stream == null || !stream.CanRead || !stream.CanSeek) + return; + + this.Initialized = Deserialize(stream); + this.SourceStream = stream; + } + + /// + /// Create a PortableExecutable object from a byte array + /// + /// Byte array representing a file + /// Positive offset representing the current position in the array + public PortableExecutable(byte[] fileContent, int offset) + { + if (fileContent == null || fileContent.Length == 0 || offset < 0) + return; + + this.Initialized = Deserialize(fileContent, offset); + this.SourceArray = fileContent; + } + + /// + /// Deserialize a PortableExecutable object from a stream + /// + /// Stream representing a file + private bool Deserialize(Stream stream) + { + try + { + // Attempt to read the DOS header first + this.DOSStubHeader = MSDOSExecutableHeader.Deserialize(stream); stream.Seek(this.DOSStubHeader.NewExeHeaderAddr, SeekOrigin.Begin); + if (this.DOSStubHeader.Magic != Constants.IMAGE_DOS_SIGNATURE) + return false; + + // If the new header address is invalid for the file, it's not a PE + if (this.DOSStubHeader.NewExeHeaderAddr >= stream.Length) + return false; + + // Then attempt to read the PE header + this.ImageFileHeader = CommonObjectFileFormatHeader.Deserialize(stream); + if (this.ImageFileHeader.Signature != Constants.IMAGE_NT_SIGNATURE) + return false; + + // If the optional header is supposed to exist, read that as well + if (this.ImageFileHeader.SizeOfOptionalHeader > 0) + this.OptionalHeader = OptionalHeader.Deserialize(stream); + + // Then read in the section table + this.SectionTable = new SectionHeader[this.ImageFileHeader.NumberOfSections]; + for (int i = 0; i < this.ImageFileHeader.NumberOfSections; i++) + { + this.SectionTable[i] = SectionHeader.Deserialize(stream); + } + + #region Structured Tables + + // // Export Table + // var table = this.GetLastSection(".edata", true); + // if (table != null && table.VirtualSize > 0) + // { + // stream.Seek((int)table.PointerToRawData, SeekOrigin.Begin); + // this.ExportTable = ExportDataSection.Deserialize(stream, this.SectionTable); + // } + + // // Import Table + // table = this.GetSection(".idata", true); + // if (table != null && table.VirtualSize > 0) + // { + // stream.Seek((int)table.PointerToRawData, SeekOrigin.Begin); + // this.ImportTable = ImportDataSection.Deserialize(stream, this.OptionalHeader.Magic == OptionalHeaderType.PE32Plus, hintCount: 0); + // } + + // Resource Table + var table = this.GetLastSection(".rsrc", true); + if (table != null && table.VirtualSize > 0) + { + stream.Seek((int)table.PointerToRawData, SeekOrigin.Begin); + this.ResourceSection = ResourceSection.Deserialize(stream, this.SectionTable); + } + + #endregion + + #region Freeform Sections + + // Data Section + this.DataSectionRaw = this.ReadRawSection(stream, ".data", force: true, first: false) ?? this.ReadRawSection(stream, "DATA", force: true, first: false); + + // Export Table + this.ExportDataSectionRaw = this.ReadRawSection(stream, ".edata", force: true, first: false); + + // Import Table + this.ImportDataSectionRaw = this.ReadRawSection(stream, ".idata", force: true, first: false); + + // Resource Data Section + this.ResourceDataSectionRaw = this.ReadRawSection(stream, ".rdata", force: true, first: false); + + // Text Section + this.TextSectionRaw = this.ReadRawSection(stream, ".text", force: true, first: false); + + #endregion + } + catch (Exception ex) + { + //Console.WriteLine($"Errored out on a file: {ex}"); + return false; + } + + return true; + } + + /// + /// Deserialize a PortableExecutable object from a byte array + /// + /// Byte array representing a file + /// Positive offset representing the current position in the array + private bool Deserialize(byte[] content, int offset) + { + try + { + // Attempt to read the DOS header first + this.DOSStubHeader = MSDOSExecutableHeader.Deserialize(content, ref offset); + offset = this.DOSStubHeader.NewExeHeaderAddr; + if (this.DOSStubHeader.Magic != Constants.IMAGE_DOS_SIGNATURE) + return false; + + // If the new header address is invalid for the file, it's not a PE + if (this.DOSStubHeader.NewExeHeaderAddr >= content.Length) + return false; + + // Then attempt to read the PE header + this.ImageFileHeader = CommonObjectFileFormatHeader.Deserialize(content, ref offset); + if (this.ImageFileHeader.Signature != Constants.IMAGE_NT_SIGNATURE) + return false; + + // If the optional header is supposed to exist, read that as well + if (this.ImageFileHeader.SizeOfOptionalHeader > 0) + this.OptionalHeader = OptionalHeader.Deserialize(content, ref offset); + + // Then read in the section table + this.SectionTable = new SectionHeader[this.ImageFileHeader.NumberOfSections]; + for (int i = 0; i < this.ImageFileHeader.NumberOfSections; i++) + { + this.SectionTable[i] = SectionHeader.Deserialize(content, ref offset); + } + + #region Structured Tables + + // // Export Table + // var table = this.GetLastSection(".edata", true); + // if (table != null && table.VirtualSize > 0) + // { + // int tableAddress = (int)table.PointerToRawData; + // this.ExportTable = ExportDataSection.Deserialize(content, ref tableAddress, this.SectionTable); + // } + + // // Import Table + // table = this.GetSection(".idata", true); + // if (table != null && table.VirtualSize > 0) + // { + // int tableAddress = (int)table.PointerToRawData; + // this.ImportTable = ImportDataSection.Deserialize(content, tableAddress, this.OptionalHeader.Magic == OptionalHeaderType.PE32Plus, hintCount: 0); + // } + + // Resource Table + var table = this.GetLastSection(".rsrc", true); + if (table != null && table.VirtualSize > 0) + { + int tableAddress = (int)table.PointerToRawData; + this.ResourceSection = ResourceSection.Deserialize(content, ref tableAddress, this.SectionTable); + } + + #endregion + + #region Freeform Sections + + // Data Section + this.DataSectionRaw = this.ReadRawSection(content, ".data", force: true, first: false) ?? this.ReadRawSection(content, "DATA", force: true, first: false); + + // Export Table + this.ExportDataSectionRaw = this.ReadRawSection(content, ".edata", force: true, first: false); + + // Import Table + this.ImportDataSectionRaw = this.ReadRawSection(content, ".idata", force: true, first: false); + + // Resource Data Section + this.ResourceDataSectionRaw = this.ReadRawSection(content, ".rdata", force: true, first: false); + + // Text Section + this.TextSectionRaw = this.ReadRawSection(content, ".text", force: true, first: false); + + #endregion + } + catch (Exception ex) + { + //Console.WriteLine($"Errored out on a file: {ex}"); + return false; + } + + return true; + } + + #endregion + #region Helpers /// @@ -356,180 +585,5 @@ namespace BurnOutSharp.ExecutableType.Microsoft } #endregion - - public static PortableExecutable Deserialize(Stream stream) - { - PortableExecutable pex = new PortableExecutable(); - - try - { - // Attempt to read the DOS header first - pex.DOSStubHeader = MSDOSExecutableHeader.Deserialize(stream); stream.Seek(pex.DOSStubHeader.NewExeHeaderAddr, SeekOrigin.Begin); - if (pex.DOSStubHeader.Magic != Constants.IMAGE_DOS_SIGNATURE) - return null; - - // If the new header address is invalid for the file, it's not a PE - if (pex.DOSStubHeader.NewExeHeaderAddr >= stream.Length) - return null; - - // Then attempt to read the PE header - pex.ImageFileHeader = CommonObjectFileFormatHeader.Deserialize(stream); - if (pex.ImageFileHeader.Signature != Constants.IMAGE_NT_SIGNATURE) - return null; - - // If the optional header is supposed to exist, read that as well - if (pex.ImageFileHeader.SizeOfOptionalHeader > 0) - pex.OptionalHeader = OptionalHeader.Deserialize(stream); - - // Then read in the section table - pex.SectionTable = new SectionHeader[pex.ImageFileHeader.NumberOfSections]; - for (int i = 0; i < pex.ImageFileHeader.NumberOfSections; i++) - { - pex.SectionTable[i] = SectionHeader.Deserialize(stream); - } - - #region Structured Tables - - // // Export Table - // var table = pex.GetLastSection(".edata", true); - // if (table != null && table.VirtualSize > 0) - // { - // stream.Seek((int)table.PointerToRawData, SeekOrigin.Begin); - // pex.ExportTable = ExportDataSection.Deserialize(stream, pex.SectionTable); - // } - - // // Import Table - // table = pex.GetSection(".idata", true); - // if (table != null && table.VirtualSize > 0) - // { - // stream.Seek((int)table.PointerToRawData, SeekOrigin.Begin); - // pex.ImportTable = ImportDataSection.Deserialize(stream, pex.OptionalHeader.Magic == OptionalHeaderType.PE32Plus, hintCount: 0); - // } - - // Resource Table - var table = pex.GetLastSection(".rsrc", true); - if (table != null && table.VirtualSize > 0) - { - stream.Seek((int)table.PointerToRawData, SeekOrigin.Begin); - pex.ResourceSection = ResourceSection.Deserialize(stream, pex.SectionTable); - } - - #endregion - - #region Freeform Sections - - // Data Section - pex.DataSectionRaw = pex.ReadRawSection(stream, ".data", force: true, first: false) ?? pex.ReadRawSection(stream, "DATA", force: true, first: false); - - // Export Table - pex.ExportDataSectionRaw = pex.ReadRawSection(stream, ".edata", force: true, first: false); - - // Import Table - pex.ImportDataSectionRaw = pex.ReadRawSection(stream, ".idata", force: true, first: false); - - // Resource Data Section - pex.ResourceDataSectionRaw = pex.ReadRawSection(stream, ".rdata", force: true, first: false); - - // Text Section - pex.TextSectionRaw = pex.ReadRawSection(stream, ".text", force: true, first: false); - - #endregion - } - catch (Exception ex) - { - //Console.WriteLine($"Errored out on a file: {ex}"); - return null; - } - - return pex; - } - - public static PortableExecutable Deserialize(byte[] content, int offset) - { - PortableExecutable pex = new PortableExecutable(); - - try - { - // Attempt to read the DOS header first - pex.DOSStubHeader = MSDOSExecutableHeader.Deserialize(content, ref offset); - offset = pex.DOSStubHeader.NewExeHeaderAddr; - if (pex.DOSStubHeader.Magic != Constants.IMAGE_DOS_SIGNATURE) - return null; - - // If the new header address is invalid for the file, it's not a PE - if (pex.DOSStubHeader.NewExeHeaderAddr >= content.Length) - return null; - - // Then attempt to read the PE header - pex.ImageFileHeader = CommonObjectFileFormatHeader.Deserialize(content, ref offset); - if (pex.ImageFileHeader.Signature != Constants.IMAGE_NT_SIGNATURE) - return null; - - // If the optional header is supposed to exist, read that as well - if (pex.ImageFileHeader.SizeOfOptionalHeader > 0) - pex.OptionalHeader = OptionalHeader.Deserialize(content, ref offset); - - // Then read in the section table - pex.SectionTable = new SectionHeader[pex.ImageFileHeader.NumberOfSections]; - for (int i = 0; i < pex.ImageFileHeader.NumberOfSections; i++) - { - pex.SectionTable[i] = SectionHeader.Deserialize(content, ref offset); - } - - #region Structured Tables - - // // Export Table - // var table = pex.GetLastSection(".edata", true); - // if (table != null && table.VirtualSize > 0) - // { - // int tableAddress = (int)table.PointerToRawData; - // pex.ExportTable = ExportDataSection.Deserialize(content, ref tableAddress, pex.SectionTable); - // } - - // // Import Table - // table = pex.GetSection(".idata", true); - // if (table != null && table.VirtualSize > 0) - // { - // int tableAddress = (int)table.PointerToRawData; - // pex.ImportTable = ImportDataSection.Deserialize(content, tableAddress, pex.OptionalHeader.Magic == OptionalHeaderType.PE32Plus, hintCount: 0); - // } - - // Resource Table - var table = pex.GetLastSection(".rsrc", true); - if (table != null && table.VirtualSize > 0) - { - int tableAddress = (int)table.PointerToRawData; - pex.ResourceSection = ResourceSection.Deserialize(content, ref tableAddress, pex.SectionTable); - } - - #endregion - - #region Freeform Sections - - // Data Section - pex.DataSectionRaw = pex.ReadRawSection(content, ".data", force: true, first: false) ?? pex.ReadRawSection(content, "DATA", force: true, first: false); - - // Export Table - pex.ExportDataSectionRaw = pex.ReadRawSection(content, ".edata", force: true, first: false); - - // Import Table - pex.ImportDataSectionRaw = pex.ReadRawSection(content, ".idata", force: true, first: false); - - // Resource Data Section - pex.ResourceDataSectionRaw = pex.ReadRawSection(content, ".rdata", force: true, first: false); - - // Text Section - pex.TextSectionRaw = pex.ReadRawSection(content, ".text", force: true, first: false); - - #endregion - } - catch (Exception ex) - { - //Console.WriteLine($"Errored out on a file: {ex}"); - return null; - } - - return pex; - } } } \ No newline at end of file diff --git a/BurnOutSharp/FileType/Executable.cs b/BurnOutSharp/FileType/Executable.cs index e1bd2b90..db292c7e 100644 --- a/BurnOutSharp/FileType/Executable.cs +++ b/BurnOutSharp/FileType/Executable.cs @@ -93,13 +93,13 @@ namespace BurnOutSharp.FileType // Create PortableExecutable and NewExecutable objects for use in the checks stream.Seek(0, SeekOrigin.Begin); - PortableExecutable pex = PortableExecutable.Deserialize(fileContent, 0); - NewExecutable nex = NewExecutable.Deserialize(fileContent, 0); + PortableExecutable pex = new PortableExecutable(fileContent, 0); + NewExecutable nex = new NewExecutable(fileContent, 0); // Create PortableExecutable and NewExecutable objects for use in the checks - // PortableExecutable pex = PortableExecutable.Deserialize(stream); + // PortableExecutable pex = new PortableExecutable(stream); // stream.Seek(0, SeekOrigin.Begin); - // NewExecutable nex = NewExecutable.Deserialize(stream); + // NewExecutable nex = new NewExecutable(stream); // stream.Seek(0, SeekOrigin.Begin); // Iterate through all content checks diff --git a/BurnOutSharp/Tools/Utilities.cs b/BurnOutSharp/Tools/Utilities.cs index 11088006..ede29346 100644 --- a/BurnOutSharp/Tools/Utilities.cs +++ b/BurnOutSharp/Tools/Utilities.cs @@ -207,7 +207,7 @@ namespace BurnOutSharp.Tools if (fileContent == null || !fileContent.Any()) return null; - return GetFileVersion(PortableExecutable.Deserialize(fileContent, 0)); + return GetFileVersion(new PortableExecutable(fileContent, 0)); } ///