From a00e6a5e2d186e09e635598bb6ab957d9f2a2377 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 11 Sep 2025 09:09:56 -0400 Subject: [PATCH] Start cleaning up resource parsing more --- .../Deserializers/PortableExecutable.cs | 145 ++++++++---------- 1 file changed, 62 insertions(+), 83 deletions(-) diff --git a/SabreTools.Serialization/Deserializers/PortableExecutable.cs b/SabreTools.Serialization/Deserializers/PortableExecutable.cs index f8b806e1..f5e59f36 100644 --- a/SabreTools.Serialization/Deserializers/PortableExecutable.cs +++ b/SabreTools.Serialization/Deserializers/PortableExecutable.cs @@ -265,18 +265,20 @@ namespace SabreTools.Serialization.Deserializers int tableSize = (int)optionalHeader.ResourceTable.Size; long paddingSize = optionalHeader.FileAlignment - ((offset + tableSize) % optionalHeader.FileAlignment); tableSize += (int)paddingSize; + tableSize = (int)Math.Min(tableSize, data.Length - offset); // Read the table data byte[]? tableData = data.ReadFrom(offset, tableSize, retainPosition: true); // Set the resource directory table long tableStart = data.Position; - pex.ResourceDirectoryTable = ParseResourceDirectoryTable(tableData, data, initialOffset, tableStart, pex.SectionTable); + int tableOffset = 0; + pex.ResourceDirectoryTable = ParseResourceDirectoryTable(tableData, ref tableOffset); #region Hidden Resources // If we have not used up the full size, parse the remaining chunk as a single resource - if (pex.ResourceDirectoryTable?.Entries != null && data.Position - tableStart < tableSize) + if (pex.ResourceDirectoryTable?.Entries != null && tableOffset < tableSize) { // Resize the entry array to accomodate one more var localEntries = pex.ResourceDirectoryTable.Entries; @@ -284,8 +286,7 @@ namespace SabreTools.Serialization.Deserializers pex.ResourceDirectoryTable.Entries = localEntries; // Get the length of the remaining data - int length = (int)(tableSize - (data.Position - tableStart)); - length = (int)Math.Min(length, data.Length - data.Position); + int length = tableSize - tableOffset; // Add the hidden entry pex.ResourceDirectoryTable.Entries[localEntries.Length - 1] = new ResourceDirectoryEntry @@ -1362,7 +1363,7 @@ namespace SabreTools.Serialization.Deserializers } /// - /// Parse a Stream into a import table + /// Parse a byte array into a import table /// /// Byte array to parse /// Filled import table on success, null on error @@ -1513,47 +1514,42 @@ namespace SabreTools.Serialization.Deserializers } /// - /// Parse a Stream into an ResourceDataEntry + /// Parse a byte array into an ResourceDataEntry /// - /// Stream to parse - /// Initial offset to use in address comparisons - /// Section table to use for virtual address translation + /// Byte array to parse + /// Offset into the byte array /// Filled ResourceDataEntry on success, null on error - public static ResourceDataEntry ParseResourceDataEntry(Stream data, long initialOffset, SectionHeader[] sections) + public static ResourceDataEntry ParseResourceDataEntry(byte[] data, ref int offset) { var obj = new ResourceDataEntry(); - obj.DataRVA = data.ReadUInt32LittleEndian(); - obj.Size = data.ReadUInt32LittleEndian(); - obj.Codepage = data.ReadUInt32LittleEndian(); - obj.Reserved = data.ReadUInt32LittleEndian(); + obj.DataRVA = data.ReadUInt32LittleEndian(ref offset); + obj.Size = data.ReadUInt32LittleEndian(ref offset); + obj.Codepage = data.ReadUInt32LittleEndian(ref offset); + obj.Reserved = data.ReadUInt32LittleEndian(ref offset); - // Empty entries are valid + // Ignore empty resources if (obj.Size == 0) - { - obj.Data = []; return obj; - } // Read the data from the offset - long offset = initialOffset + obj.DataRVA.ConvertVirtualAddress(sections); - if (offset > initialOffset && offset + obj.Size < data.Length) + if (obj.DataRVA > 0 && obj.DataRVA + obj.Size < data.Length) { - data.Seek(offset, SeekOrigin.Begin); - obj.Data = data.ReadBytes((int)obj.Size); + int dataOffset = (int)obj.DataRVA; + obj.Data = data.ReadBytes(ref dataOffset, (int)obj.Size); } return obj; } /// - /// Parse a Stream into an ResourceDirectoryEntry + /// Parse a byte array into an ResourceDirectoryEntry /// - /// Stream to parse + /// Byte array to parse + /// Offset into the byte array /// Indicates if the value is a name entry or not - /// Table start address for relative reads /// Filled ResourceDirectoryEntry on success, null on error - public static ResourceDirectoryEntry ParseResourceDirectoryEntry(Stream data, bool nameEntry, long tableStart) + public static ResourceDirectoryEntry ParseResourceDirectoryEntry(byte[] data, ref int offset, bool nameEntry) { var obj = new ResourceDirectoryEntry(); @@ -1563,74 +1559,58 @@ namespace SabreTools.Serialization.Deserializers // documentation makes no mention of the high bit being set here, // only for the offset below. if (nameEntry) - obj.NameOffset = data.ReadUInt32LittleEndian() & ~0x80000000U; + obj.NameOffset = data.ReadUInt32LittleEndian(ref offset) & ~0x80000000U; else - obj.IntegerID = data.ReadUInt32LittleEndian(); + obj.IntegerID = data.ReadUInt32LittleEndian(ref offset); - uint offset = data.ReadUInt32LittleEndian(); - if ((offset & 0x80000000) != 0) - obj.SubdirectoryOffset = offset & ~0x80000000; + uint offsetField = data.ReadUInt32LittleEndian(ref offset); + if ((offsetField & 0x80000000) != 0) + obj.SubdirectoryOffset = offsetField & ~0x80000000; else - obj.DataEntryOffset = offset; - - // Read the name from the offset, if needed - if (nameEntry && obj.NameOffset > 0) - { - long nameOffset = tableStart + obj.NameOffset; - if (nameOffset > tableStart && nameOffset < data.Length) - { - long currentOffset = data.Position; - data.Seek(nameOffset, SeekOrigin.Begin); - obj.Name = ParseResourceDirectoryString(data); - data.Seek(currentOffset, SeekOrigin.Begin); - } - } + obj.DataEntryOffset = offsetField; return obj; } /// - /// Parse a Stream into an ResourceDirectoryString + /// Parse a byte array into an ResourceDirectoryString /// - /// Stream to parse + /// Byte array to parse + /// Offset into the byte array /// Filled ResourceDirectoryString on success, null on error - public static ResourceDirectoryString ParseResourceDirectoryString(Stream data) + public static ResourceDirectoryString ParseResourceDirectoryString(byte[] data, ref int offset) { var obj = new ResourceDirectoryString(); - obj.Length = data.ReadUInt16LittleEndian(); - if (obj.Length > 0 && data.Position + (obj.Length * 2) <= data.Length) - obj.UnicodeString = data.ReadBytes(obj.Length * 2); + obj.Length = data.ReadUInt16LittleEndian(ref offset); + if (obj.Length > 0 && offset + (obj.Length * 2) <= data.Length) + obj.UnicodeString = data.ReadBytes(ref offset, obj.Length * 2); return obj; } /// - /// Parse a Stream into a ResourceDirectoryTable + /// Parse a byte array into a ResourceDirectoryTable /// - /// Byte array representing the table data - /// Stream to use for additional parsing - /// Initial offset to use in address comparisons - /// Table start address for relative reads - /// Section table to use for virtual address translation + /// Byte array to parse + /// Offset into the byte array /// Filled ResourceDirectoryTable on success, null on error - public static ResourceDirectoryTable? ParseResourceDirectoryTable(byte[]? tableData, - Stream data, - long initialOffset, - long tableStart, - SectionHeader[] sections) + public static ResourceDirectoryTable? ParseResourceDirectoryTable(byte[]? tableData, ref int offset) { + if (tableData == null) + return null; + var obj = new ResourceDirectoryTable(); - obj.Characteristics = data.ReadUInt32LittleEndian(); + obj.Characteristics = tableData.ReadUInt32LittleEndian(ref offset); if (obj.Characteristics != 0) return null; - obj.TimeDateStamp = data.ReadUInt32LittleEndian(); - obj.MajorVersion = data.ReadUInt16LittleEndian(); - obj.MinorVersion = data.ReadUInt16LittleEndian(); - obj.NumberOfNameEntries = data.ReadUInt16LittleEndian(); - obj.NumberOfIDEntries = data.ReadUInt16LittleEndian(); + obj.TimeDateStamp = tableData.ReadUInt32LittleEndian(ref offset); + obj.MajorVersion = tableData.ReadUInt16LittleEndian(ref offset); + obj.MinorVersion = tableData.ReadUInt16LittleEndian(ref offset); + obj.NumberOfNameEntries = tableData.ReadUInt16LittleEndian(ref offset); + obj.NumberOfIDEntries = tableData.ReadUInt16LittleEndian(ref offset); // Create the entry array int totalEntryCount = obj.NumberOfNameEntries + obj.NumberOfIDEntries; @@ -1642,29 +1622,28 @@ namespace SabreTools.Serialization.Deserializers for (int i = 0; i < totalEntryCount; i++) { bool nameEntry = i < obj.NumberOfNameEntries; - obj.Entries[i] = ParseResourceDirectoryEntry(data, nameEntry, tableStart); + obj.Entries[i] = ParseResourceDirectoryEntry(tableData, ref offset, nameEntry); + + // Read the name from the offset, if needed + if (nameEntry && obj.Entries[i].NameOffset > 0 && obj.Entries[i].NameOffset < tableData.Length) + { + int nameOffset = (int)obj.Entries[i].NameOffset; + obj.Entries[i].Name = ParseResourceDirectoryString(tableData, ref nameOffset); + } } // Loop through and process the entries foreach (var entry in obj.Entries) { - if (entry.DataEntryOffset > 0) + if (entry.DataEntryOffset > 0 && entry.DataEntryOffset < tableData.Length) { - long offset = tableStart + entry.DataEntryOffset; - if (offset > tableStart && offset < data.Length) - { - data.Seek(offset, SeekOrigin.Begin); - entry.DataEntry = ParseResourceDataEntry(data, initialOffset, sections); ; - } + int entryOffset = (int)entry.DataEntryOffset; + entry.DataEntry = ParseResourceDataEntry(tableData, ref entryOffset); } - else if (entry.SubdirectoryOffset > 0) + else if (entry.SubdirectoryOffset > 0 && entry.SubdirectoryOffset < tableData.Length) { - long offset = tableStart + entry.SubdirectoryOffset; - if (offset > tableStart && offset < data.Length) - { - data.Seek(offset, SeekOrigin.Begin); - entry.Subdirectory = ParseResourceDirectoryTable(tableData, data, initialOffset, tableStart, sections); - } + int subdirOffset = (int)entry.SubdirectoryOffset; + entry.Subdirectory = ParseResourceDirectoryTable(tableData, ref subdirOffset); } }