From 4d800fd644240438a2b8637e8def85fcb2ae91c0 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 9 Sep 2021 11:25:02 -0700 Subject: [PATCH] Fix ResourceDirectoryString reading --- .../Entries/ResourceDirectoryString.cs | 17 ++- .../Entries/ResourceDirectoryTableEntry.cs | 114 ++++++++++-------- .../Microsoft/PortableExecutable.cs | 101 +++++++++++++--- .../Microsoft/Sections/ExportDataSection.cs | 16 +-- .../Microsoft/Sections/ResourceSection.cs | 10 +- .../Tables/ResourceDirectoryTable.cs | 12 +- 6 files changed, 186 insertions(+), 84 deletions(-) diff --git a/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryString.cs b/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryString.cs index b6116213..31544743 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryString.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryString.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Runtime.InteropServices; using System.Text; using BurnOutSharp.Tools; @@ -21,14 +22,24 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Entries /// /// The variable-length Unicode string data, word-aligned. /// - public char[] UnicodeString; + public string UnicodeString; public static ResourceDirectoryString Deserialize(Stream stream) { var rds = new ResourceDirectoryString(); rds.Length = stream.ReadUInt16(); - rds.UnicodeString = stream.ReadChars(rds.Length, Encoding.Unicode); + rds.UnicodeString = new string(stream.ReadChars(rds.Length, Encoding.Unicode)); + + return rds; + } + + public static ResourceDirectoryString Deserialize(byte[] content, int offset) + { + var rds = new ResourceDirectoryString(); + + rds.Length = BitConverter.ToUInt16(content, offset); offset += 2; + rds.UnicodeString = Encoding.Unicode.GetString(content, offset, rds.Length); return rds; } diff --git a/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryTableEntry.cs b/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryTableEntry.cs index b48383a3..334addd3 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryTableEntry.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryTableEntry.cs @@ -22,12 +22,17 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Entries /// /// The offset of a string that gives the Type, Name, or Language ID entry, depending on level of table. /// - public uint NameOffset; + public uint NameOffset => (uint)(IntegerId & (1 << 32)); + + /// + /// The string that gives the Type, Name, or Language ID entry, depending on level of table pointed to by NameOffset + /// + public ResourceDirectoryString Name; /// /// A 32-bit integer that identifies the Type, Name, or Language ID entry. /// - public uint IntegerId => NameOffset; + public uint IntegerId; /// /// High bit 0. Address of a Resource Data entry (a leaf). @@ -37,42 +42,66 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Entries /// /// High bit 1. The lower 31 bits are the address of another resource directory table (the next level down). /// - public uint SubdirectoryOffset => DataEntryOffset; + public uint SubdirectoryOffset => (uint)(DataEntryOffset & (1 << 32)); /// /// Resource Data entry (a leaf). /// public ResourceDataEntry DataEntry; + private bool NameFieldIsIntegerId = false; + + /// + /// Determine if an entry has a name or integer identifier + /// + public bool IsIntegerIDEntry() => (NameOffset & (1 << 32)) == 0; + /// /// Determine if an entry represents a leaf or another directory table /// - public bool IsResourceDataEntry() => (DataEntryOffset & (1 << 31)) == 0; + public bool IsResourceDataEntry() => (DataEntryOffset & (1 << 32)) == 0; - public static ResourceDirectoryTableEntry Deserialize(Stream stream, SectionHeader[] sections) + public static ResourceDirectoryTableEntry Deserialize(Stream stream, long sectionStart) { var rdte = new ResourceDirectoryTableEntry(); - rdte.NameOffset = stream.ReadUInt32(); - rdte.DataEntryOffset = stream.ReadUInt32(); + rdte.IntegerId = stream.ReadUInt32(); + if (!rdte.IsIntegerIDEntry()) + { + long lastPosition = stream.Position; + int nameAddress = (int)(rdte.NameOffset + sectionStart); + if (nameAddress >= 0 && nameAddress < stream.Length) + { + try + { + stream.Seek(nameAddress, SeekOrigin.Begin); + rdte.Name = ResourceDirectoryString.Deserialize(stream); + } + catch { } + finally + { + stream.Seek(lastPosition, SeekOrigin.Begin); + } + } + } - // Read in the data if we have a leaf + rdte.DataEntryOffset = stream.ReadUInt32(); if (rdte.IsResourceDataEntry()) { long lastPosition = stream.Position; - try + int dataEntryAddress = (int)(rdte.DataEntryOffset + sectionStart); + if (dataEntryAddress > 0 && dataEntryAddress < stream.Length) { - int dataEntryAddress = (int)ConvertVirtualAddress(rdte.DataEntryOffset, sections); - if (dataEntryAddress > 0) + try { stream.Seek(dataEntryAddress, SeekOrigin.Begin); rdte.DataEntry = ResourceDataEntry.Deserialize(stream); } - } - catch { } - finally - { - stream.Seek(lastPosition, SeekOrigin.Begin); + catch { } + finally + { + stream.Seek(lastPosition, SeekOrigin.Begin); + } } } @@ -81,52 +110,41 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Entries return rdte; } - public static ResourceDirectoryTableEntry Deserialize(byte[] content, int offset, SectionHeader[] sections) + public static ResourceDirectoryTableEntry Deserialize(byte[] content, int offset, long sectionStart) { var rdte = new ResourceDirectoryTableEntry(); - rdte.NameOffset = BitConverter.ToUInt32(content, offset); offset += 4; - rdte.DataEntryOffset = BitConverter.ToUInt32(content, offset); offset += 4; + rdte.IntegerId = BitConverter.ToUInt32(content, offset); offset += 4; + if (!rdte.IsIntegerIDEntry()) + { + int nameAddress = (int)(rdte.NameOffset + sectionStart); + if (nameAddress >= 0 && nameAddress < content.Length) + { + try + { + rdte.Name = ResourceDirectoryString.Deserialize(content, nameAddress); + } + catch { } + } + } - // Read in the data if we have a leaf + rdte.DataEntryOffset = BitConverter.ToUInt32(content, offset); offset += 4; if (rdte.IsResourceDataEntry()) { - try + int dataEntryAddress = (int)(rdte.DataEntryOffset + sectionStart); + if (dataEntryAddress > 0 && dataEntryAddress < content.Length) { - int dataEntryAddress = (int)ConvertVirtualAddress(rdte.DataEntryOffset, sections); - if (dataEntryAddress > 0) + try + { rdte.DataEntry = ResourceDataEntry.Deserialize(content, dataEntryAddress); + } + catch { } } - catch { } } // TODO: Add parsing for further directory table entries in the tree return rdte; } - - /// - /// Convert a virtual address to a physical one - /// - /// Virtual address to convert - /// Array of sections to check against - /// Physical address, 0 on error - private static uint ConvertVirtualAddress(uint virtualAddress, SectionHeader[] sections) - { - // Loop through all of the sections - for (int i = 0; i < sections.Length; i++) - { - // If the section is invalid, just skip it - if (sections[i] == null) - continue; - - // Attempt to derive the physical address from the current section - var section = sections[i]; - if (virtualAddress >= section.VirtualAddress && virtualAddress <= section.VirtualAddress + section.VirtualSize) - return section.PointerToRawData + virtualAddress - section.VirtualAddress; - } - - return 0; - } } } \ No newline at end of file diff --git a/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs b/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs index 5a796d11..7a63a5a5 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs @@ -83,7 +83,7 @@ namespace BurnOutSharp.ExecutableType.Microsoft /// /// Name of the section to check for /// True to enable exact matching of names, false for starts-with - /// Tuple of contains and index + /// True if the section is in the executable, false otherwise public bool ContainsSection(string sectionName, bool exact = false) { // Get all section names first @@ -100,6 +100,27 @@ namespace BurnOutSharp.ExecutableType.Microsoft return sectionNames.Any(n => n.Trim('\0').StartsWith(sectionName)); } + /// + /// Get the section based on name, if possible + /// + /// Name of the section to check for + /// True to enable exact matching of names, false for starts-with + /// Section data on success, null on error + public SectionHeader GetSection(string sectionName, bool exact = false) + { + // If we have no sections, we can't do anything + if (SectionTable == null || !SectionTable.Any()) + return null; + + // If we're checking exactly, return only exact matches (with nulls trimmed) + if (exact) + return SectionTable.FirstOrDefault(s => Encoding.ASCII.GetString(s.Name).Trim('\0').Equals(sectionName)); + + // Otherwise, check if section name starts with the value + else + return SectionTable.FirstOrDefault(s => Encoding.ASCII.GetString(s.Name).Trim('\0').StartsWith(sectionName)); + } + /// /// Get the list of section names /// @@ -170,6 +191,33 @@ namespace BurnOutSharp.ExecutableType.Microsoft // stream.Seek(tableAddress, SeekOrigin.Begin); // pex.ResourceSection = ResourceSection.Deserialize(stream, pex.SectionTable); // } + + // // Export Table + // var table = pex.GetSection(".edata", true); + // if (table != null && table.VirtualSize > 0) + // { + // int tableAddress = (int)ConvertVirtualAddress(table.VirtualAddress, pex.SectionTable); + // stream.Seek(tableAddress, SeekOrigin.Begin); + // pex.ExportTable = ExportDataSection.Deserialize(stream); + // } + + // // Import Table + // table = pex.GetSection(".idata", true); + // if (table != null && table.VirtualSize > 0) + // { + // int tableAddress = (int)ConvertVirtualAddress(table.VirtualAddress, pex.SectionTable); + // stream.Seek(tableAddress, SeekOrigin.Begin); + // pex.ImportTable = ImportDataSection.Deserialize(stream, pex.OptionalHeader.Magic == OptionalHeaderType.PE32Plus, hintCount: 0); + // } + + // Resource Table + var table = pex.GetSection(".rsrc", true); + if (table != null && table.VirtualSize > 0) + { + int tableAddress = (int)ConvertVirtualAddress(table.VirtualAddress, pex.SectionTable); + stream.Seek(tableAddress, SeekOrigin.Begin); + pex.ResourceSection = ResourceSection.Deserialize(stream); + } } catch (Exception ex) { @@ -216,30 +264,29 @@ namespace BurnOutSharp.ExecutableType.Microsoft pex.SectionTable[i] = SectionHeader.Deserialize(content, offset); offset += 40; } - // TODO: Uncomment these as the directories are understod and implemented // // Export Table - // var table = pex.SectionTable[(byte)ImageDirectory.IMAGE_DIRECTORY_ENTRY_EXPORT]; - // if (table.VirtualSize > 0) + // var table = pex.GetSection(".edata", true); + // if (table != null && table.VirtualSize > 0) // { - // int tableAddress = (int)EVORE.ConvertVirtualAddress(table.VirtualAddress, pex.SectionTable); + // int tableAddress = (int)ConvertVirtualAddress(table.VirtualAddress, pex.SectionTable); // pex.ExportTable = ExportDataSection.Deserialize(content, tableAddress); // } // // Import Table - // table = pex.SectionTable[(byte)ImageDirectory.IMAGE_DIRECTORY_ENTRY_IMPORT]; - // if (table.VirtualSize > 0) + // table = pex.GetSection(".idata", true); + // if (table != null && table.VirtualSize > 0) // { - // int tableAddress = (int)EVORE.ConvertVirtualAddress(table.VirtualAddress, pex.SectionTable); + // int tableAddress = (int)ConvertVirtualAddress(table.VirtualAddress, pex.SectionTable); // pex.ImportTable = ImportDataSection.Deserialize(content, tableAddress, pex.OptionalHeader.Magic == OptionalHeaderType.PE32Plus, hintCount: 0); // } - // // Resource Table - // var table = pex.SectionTable[(byte)ImageDirectory.IMAGE_DIRECTORY_ENTRY_RESOURCE]; - // if (table.VirtualSize > 0) - // { - // int tableAddress = (int)EVORE.ConvertVirtualAddress(table.VirtualAddress, pex.SectionTable); - // pex.ResourceSection = ResourceSection.Deserialize(content, tableAddress, pex.SectionTable); - // } + // Resource Table + var table = pex.GetSection(".rsrc", true); + if (table != null && table.VirtualSize > 0) + { + int tableAddress = (int)ConvertVirtualAddress(table.VirtualAddress, pex.SectionTable); + pex.ResourceSection = ResourceSection.Deserialize(content, tableAddress); + } } } catch (Exception ex) @@ -250,5 +297,29 @@ namespace BurnOutSharp.ExecutableType.Microsoft return pex; } + + /// + /// Convert a virtual address to a physical one + /// + /// Virtual address to convert + /// Array of sections to check against + /// Physical address, 0 on error + public static uint ConvertVirtualAddress(uint virtualAddress, SectionHeader[] sections) + { + // Loop through all of the sections + for (int i = 0; i < sections.Length; i++) + { + // If the section is invalid, just skip it + if (sections[i] == null) + continue; + + // Attempt to derive the physical address from the current section + var section = sections[i]; + if (virtualAddress >= section.VirtualAddress && virtualAddress <= section.VirtualAddress + section.VirtualSize) + return section.PointerToRawData + virtualAddress - section.VirtualAddress; + } + + return 0; + } } } \ No newline at end of file diff --git a/BurnOutSharp/ExecutableType/Microsoft/Sections/ExportDataSection.cs b/BurnOutSharp/ExecutableType/Microsoft/Sections/ExportDataSection.cs index 23fd4c5c..197b414f 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/Sections/ExportDataSection.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/Sections/ExportDataSection.cs @@ -48,10 +48,10 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Sections var eds = new ExportDataSection(); eds.ExportDirectoryTable = ExportDirectoryTable.Deserialize(stream); - eds.ExportAddressTable = ExportAddressTable.Deserialize(stream, count: 0); // TODO: Figure out where this count comes from - eds.NamePointerTable = ExportNamePointerTable.Deserialize(stream, count: 0); // TODO: Figure out where this count comes from - eds.OrdinalTable = ExportOrdinalTable.Deserialize(stream, count: 0); // TODO: Figure out where this count comes from - //eds.ExportNameTable = ExportNameTable.Deserialize(stream); // TODO: set this table based on the NamePointerTable value + // eds.ExportAddressTable = ExportAddressTable.Deserialize(stream, count: 0); // TODO: Figure out where this count comes from + // eds.NamePointerTable = ExportNamePointerTable.Deserialize(stream, count: 0); // TODO: Figure out where this count comes from + // eds.OrdinalTable = ExportOrdinalTable.Deserialize(stream, count: 0); // TODO: Figure out where this count comes from + // eds.ExportNameTable = ExportNameTable.Deserialize(stream); // TODO: set this table based on the NamePointerTable value return eds; } @@ -63,10 +63,10 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Sections unsafe { eds.ExportDirectoryTable = ExportDirectoryTable.Deserialize(content, offset); offset += Marshal.SizeOf(eds.ExportDirectoryTable); - eds.ExportAddressTable = ExportAddressTable.Deserialize(content, offset, count: 0); offset += Marshal.SizeOf(eds.ExportAddressTable); // TODO: Figure out where this count comes from - eds.NamePointerTable = ExportNamePointerTable.Deserialize(content, offset, count: 0); offset += Marshal.SizeOf(eds.NamePointerTable); // TODO: Figure out where this count comes from - eds.OrdinalTable = ExportOrdinalTable.Deserialize(content, offset, count: 0); offset += Marshal.SizeOf(eds.OrdinalTable); // TODO: Figure out where this count comes from - //eds.ExportNameTable = ExportNameTable.Deserialize(stream); offset += Marshal.SizeOf(eds.ExportAddressTable); // TODO: set this table based on the NamePointerTable value + // eds.ExportAddressTable = ExportAddressTable.Deserialize(content, offset, count: 0); offset += Marshal.SizeOf(eds.ExportAddressTable); // TODO: Figure out where this count comes from + // eds.NamePointerTable = ExportNamePointerTable.Deserialize(content, offset, count: 0); offset += Marshal.SizeOf(eds.NamePointerTable); // TODO: Figure out where this count comes from + // eds.OrdinalTable = ExportOrdinalTable.Deserialize(content, offset, count: 0); offset += Marshal.SizeOf(eds.OrdinalTable); // TODO: Figure out where this count comes from + // eds.ExportNameTable = ExportNameTable.Deserialize(stream); offset += Marshal.SizeOf(eds.ExportAddressTable); // TODO: set this table based on the NamePointerTable value } return eds; diff --git a/BurnOutSharp/ExecutableType/Microsoft/Sections/ResourceSection.cs b/BurnOutSharp/ExecutableType/Microsoft/Sections/ResourceSection.cs index 134bada8..cfea6738 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/Sections/ResourceSection.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/Sections/ResourceSection.cs @@ -22,22 +22,24 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Sections /// public ResourceDirectoryTable ResourceDirectoryTable; - public static ResourceSection Deserialize(Stream stream, SectionHeader[] sections) + public static ResourceSection Deserialize(Stream stream) { var rs = new ResourceSection(); - rs.ResourceDirectoryTable = ResourceDirectoryTable.Deserialize(stream, sections); + long sectionStart = stream.Position; + rs.ResourceDirectoryTable = ResourceDirectoryTable.Deserialize(stream, sectionStart); return rs; } - public static ResourceSection Deserialize(byte[] content, int offset, SectionHeader[] sections) + public static ResourceSection Deserialize(byte[] content, int offset) { var rs = new ResourceSection(); unsafe { - rs.ResourceDirectoryTable = ResourceDirectoryTable.Deserialize(content, offset, sections); offset += Marshal.SizeOf(rs.ResourceDirectoryTable); + long sectionStart = offset; + rs.ResourceDirectoryTable = ResourceDirectoryTable.Deserialize(content, offset, sectionStart); offset += Marshal.SizeOf(rs.ResourceDirectoryTable); } return rs; diff --git a/BurnOutSharp/ExecutableType/Microsoft/Tables/ResourceDirectoryTable.cs b/BurnOutSharp/ExecutableType/Microsoft/Tables/ResourceDirectoryTable.cs index 411e261b..3110ff61 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/Tables/ResourceDirectoryTable.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/Tables/ResourceDirectoryTable.cs @@ -68,7 +68,7 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Tables // TODO: Determine how to store or reference the resource directory strings // that immediately follow the last directory entry but before the data - public static ResourceDirectoryTable Deserialize(Stream stream, SectionHeader[] sections) + public static ResourceDirectoryTable Deserialize(Stream stream, long sectionStart) { var rdt = new ResourceDirectoryTable(); @@ -82,19 +82,19 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Tables rdt.NamedEntries = new ResourceDirectoryTableEntry[rdt.NumberOfNamedEntries]; for (int i = 0; i < rdt.NumberOfNamedEntries; i++) { - rdt.NamedEntries[i] = ResourceDirectoryTableEntry.Deserialize(stream, sections); + rdt.NamedEntries[i] = ResourceDirectoryTableEntry.Deserialize(stream, sectionStart); } rdt.IdEntries = new ResourceDirectoryTableEntry[rdt.NumberOfIdEntries]; for (int i = 0; i < rdt.NumberOfIdEntries; i++) { - rdt.IdEntries[i] = ResourceDirectoryTableEntry.Deserialize(stream, sections); + rdt.IdEntries[i] = ResourceDirectoryTableEntry.Deserialize(stream, sectionStart); } return rdt; } - public static ResourceDirectoryTable Deserialize(byte[] content, int offset, SectionHeader[] sections) + public static ResourceDirectoryTable Deserialize(byte[] content, int offset, long sectionStart) { var rdt = new ResourceDirectoryTable(); @@ -108,13 +108,13 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Tables rdt.NamedEntries = new ResourceDirectoryTableEntry[rdt.NumberOfNamedEntries]; for (int i = 0; i < rdt.NumberOfNamedEntries; i++) { - rdt.NamedEntries[i] = ResourceDirectoryTableEntry.Deserialize(content, offset, sections); offset += 8; + rdt.NamedEntries[i] = ResourceDirectoryTableEntry.Deserialize(content, offset, sectionStart); offset += 8; } rdt.IdEntries = new ResourceDirectoryTableEntry[rdt.NumberOfIdEntries]; for (int i = 0; i < rdt.NumberOfIdEntries; i++) { - rdt.IdEntries[i] = ResourceDirectoryTableEntry.Deserialize(content, offset, sections); offset += 8; + rdt.IdEntries[i] = ResourceDirectoryTableEntry.Deserialize(content, offset, sectionStart); offset += 8; } return rdt;