diff --git a/BurnOutSharp.Builder/Extensions.cs b/BurnOutSharp.Builder/Extensions.cs index 97fd823c..3c41afbc 100644 --- a/BurnOutSharp.Builder/Extensions.cs +++ b/BurnOutSharp.Builder/Extensions.cs @@ -330,15 +330,16 @@ namespace BurnOutSharp.Builder #endregion + // TODO: Write extension to parse resource data #region Portable Executable /// - /// Convert a virtual address to a physical one + /// Convert a relative virtual address to a physical one /// - /// Virtual address to convert + /// Relative virtual address to convert /// Array of sections to check against /// Physical address, 0 on error - public static uint ConvertVirtualAddress(this uint virtualAddress, Models.PortableExecutable.SectionHeader[] sections) + public static uint ConvertVirtualAddress(this uint rva, Models.PortableExecutable.SectionHeader[] sections) { // Loop through all of the sections for (int i = 0; i < sections.Length; i++) @@ -353,8 +354,8 @@ namespace BurnOutSharp.Builder // 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; + if (rva >= section.VirtualAddress && rva <= section.VirtualAddress + section.VirtualSize) + return rva - section.VirtualAddress + section.PointerToRawData; } return 0; diff --git a/BurnOutSharp.Builder/PortableExecutable.cs b/BurnOutSharp.Builder/PortableExecutable.cs index cabadba5..37ccf6e6 100644 --- a/BurnOutSharp.Builder/PortableExecutable.cs +++ b/BurnOutSharp.Builder/PortableExecutable.cs @@ -114,6 +114,33 @@ namespace BurnOutSharp.Builder #endregion + // TODO: COFFStringTable (Only if COFFSymbolTable?) + // TODO: AttributeCertificateTable + // TODO: DelayLoadDirectoryTable + + // TODO: Port to byte once done + #region Resource Directory Table + + // Should also be in the '.rsrc' section + if (optionalHeader.ResourceTable != null && optionalHeader.ResourceTable.VirtualAddress != 0) + { + // If the offset for the resource directory table doesn't exist + int tableAddress = initialOffset + + (int)optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (tableAddress >= data.Length) + return executable; + + // Try to parse the resource directory table + var resourceDirectoryTable = ParseResourceDirectoryTable(data, tableAddress, tableAddress, executable.SectionTable); + if (resourceDirectoryTable == null) + return null; + + // Set the resource directory table + executable.ResourceDirectoryTable = resourceDirectoryTable; + } + + #endregion + // TODO: Finish implementing PE parsing return executable; } @@ -507,6 +534,146 @@ namespace BurnOutSharp.Builder return coffSymbolTable; } + /// + /// Parse a byte array into a resource directory table + /// + /// Byte array to parse + /// Offset into the byte array + /// Initial offset to use in address comparisons + /// Section table to use for virtual address translation + /// Filled resource directory table on success, null on error + private static ResourceDirectoryTable ParseResourceDirectoryTable(byte[] data, int offset, long initialOffset, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var resourceDirectoryTable = new ResourceDirectoryTable(); + + resourceDirectoryTable.Characteristics = data.ReadUInt32(ref offset); + resourceDirectoryTable.TimeDateStamp = data.ReadUInt32(ref offset); + resourceDirectoryTable.MajorVersion = data.ReadUInt16(ref offset); + resourceDirectoryTable.MinorVersion = data.ReadUInt16(ref offset); + resourceDirectoryTable.NumberOfNameEntries = data.ReadUInt16(ref offset); + resourceDirectoryTable.NumberOfIDEntries = data.ReadUInt16(ref offset); + + // Perform top-level pass of data + if (resourceDirectoryTable.NumberOfNameEntries > 0) + { + resourceDirectoryTable.NameEntries = new ResourceDirectoryEntry[resourceDirectoryTable.NumberOfNameEntries]; + for (int i = 0; i < resourceDirectoryTable.NumberOfNameEntries; i++) + { + var entry = new ResourceDirectoryEntry(); + entry.NameOffset = data.ReadUInt32(ref offset); + + uint newOffset = data.ReadUInt32(ref offset); + if ((newOffset & 0xF0000000) != 0) + entry.SubdirectoryOffset = newOffset & ~0xF0000000; + else + entry.DataEntryOffset = newOffset; + + // Read the name from the offset + int currentOffset = offset; + offset = (int)(entry.NameOffset + initialOffset); + var resourceDirectoryString = new ResourceDirectoryString(); + resourceDirectoryString.Length = data.ReadUInt16(ref offset); + resourceDirectoryString.UnicodeString = data.ReadBytes(ref offset, resourceDirectoryString.Length); + entry.Name = resourceDirectoryString; + offset = currentOffset; + + resourceDirectoryTable.NameEntries[i] = entry; + } + } + if (resourceDirectoryTable.NumberOfIDEntries > 0) + { + resourceDirectoryTable.IDEntries = new ResourceDirectoryEntry[resourceDirectoryTable.NumberOfIDEntries]; + for (int i = 0; i < resourceDirectoryTable.NumberOfIDEntries; i++) + { + var entry = new ResourceDirectoryEntry(); + entry.IntegerID = data.ReadUInt32(ref offset); + + uint newOffset = data.ReadUInt32(ref offset); + if ((newOffset & 0xF0000000) != 0) + entry.SubdirectoryOffset = newOffset & ~0xF0000000; + else + entry.DataEntryOffset = newOffset; + + resourceDirectoryTable.IDEntries[i] = entry; + } + } + + // Read all leaves at this level + if (resourceDirectoryTable.NumberOfNameEntries > 0) + { + foreach (var entry in resourceDirectoryTable.NameEntries) + { + if (entry.SubdirectoryOffset != 0) + continue; + + int newOffset = (int)(entry.DataEntryOffset + initialOffset); + + var resourceDataEntry = new ResourceDataEntry(); + resourceDataEntry.DataRVA = data.ReadUInt32(ref newOffset); + resourceDataEntry.Size = data.ReadUInt32(ref newOffset); + resourceDataEntry.Codepage = data.ReadUInt32(ref offset); + resourceDataEntry.Reserved = data.ReadUInt32(ref offset); + + // Read the data from the offset + newOffset = (int)resourceDataEntry.DataRVA.ConvertVirtualAddress(sections); + if (newOffset > 0) + resourceDataEntry.Data = data.ReadBytes(ref newOffset, (int)resourceDataEntry.Size); + + entry.DataEntry = resourceDataEntry; + } + } + if (resourceDirectoryTable.NumberOfIDEntries > 0) + { + foreach (var entry in resourceDirectoryTable.IDEntries) + { + if (entry.SubdirectoryOffset != 0) + continue; + + int newOffset = (int)(entry.DataEntryOffset + initialOffset); + + var resourceDataEntry = new ResourceDataEntry(); + resourceDataEntry.DataRVA = data.ReadUInt32(ref newOffset); + resourceDataEntry.Size = data.ReadUInt32(ref newOffset); + resourceDataEntry.Codepage = data.ReadUInt32(ref newOffset); + resourceDataEntry.Reserved = data.ReadUInt32(ref newOffset); + + // Read the data from the offset + newOffset = (int)resourceDataEntry.DataRVA.ConvertVirtualAddress(sections); + if (newOffset > 0) + resourceDataEntry.Data = data.ReadBytes(ref newOffset, (int)resourceDataEntry.Size); + + entry.DataEntry = resourceDataEntry; + } + } + + // Now go one level lower + if (resourceDirectoryTable.NumberOfNameEntries > 0) + { + foreach (var entry in resourceDirectoryTable.NameEntries) + { + if (entry.DataEntryOffset != 0) + continue; + + int newOffset = (int)(entry.SubdirectoryOffset + initialOffset); + entry.Subdirectory = ParseResourceDirectoryTable(data, newOffset, initialOffset, sections); + } + } + if (resourceDirectoryTable.NumberOfIDEntries > 0) + { + foreach (var entry in resourceDirectoryTable.IDEntries) + { + if (entry.DataEntryOffset != 0) + continue; + + int newOffset = (int)(entry.SubdirectoryOffset + initialOffset); + entry.Subdirectory = ParseResourceDirectoryTable(data, newOffset, initialOffset, sections); + } + } + + return resourceDirectoryTable; + } + #endregion #region Stream Data @@ -616,6 +783,34 @@ namespace BurnOutSharp.Builder #endregion + // TODO: COFFStringTable (Only if COFFSymbolTable?) + // TODO: AttributeCertificateTable + // TODO: DelayLoadDirectoryTable + + // TODO: Port to byte once done + #region Resource Directory Table + + // Should also be in the '.rsrc' section + if (optionalHeader.ResourceTable != null && optionalHeader.ResourceTable.VirtualAddress != 0) + { + // If the offset for the resource directory table doesn't exist + int tableAddress = initialOffset + + (int)optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (tableAddress >= data.Length) + return executable; + + // Try to parse the resource directory table + data.Seek(tableAddress, SeekOrigin.Begin); + var resourceDirectoryTable = ParseResourceDirectoryTable(data, data.Position, executable.SectionTable); + if (resourceDirectoryTable == null) + return null; + + // Set the resource directory table + executable.ResourceDirectoryTable = resourceDirectoryTable; + } + + #endregion + // TODO: Finish implementing PE parsing return executable; } @@ -1005,6 +1200,156 @@ namespace BurnOutSharp.Builder return coffSymbolTable; } + /// + /// Parse a Stream into a resource directory table + /// + /// Stream to parse + /// Initial offset to use in address comparisons + /// Section table to use for virtual address translation + /// Filled resource directory table on success, null on error + private static ResourceDirectoryTable ParseResourceDirectoryTable(Stream data, long initialOffset, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var resourceDirectoryTable = new ResourceDirectoryTable(); + + resourceDirectoryTable.Characteristics = data.ReadUInt32(); + resourceDirectoryTable.TimeDateStamp = data.ReadUInt32(); + resourceDirectoryTable.MajorVersion = data.ReadUInt16(); + resourceDirectoryTable.MinorVersion = data.ReadUInt16(); + resourceDirectoryTable.NumberOfNameEntries = data.ReadUInt16(); + resourceDirectoryTable.NumberOfIDEntries = data.ReadUInt16(); + + // Perform top-level pass of data + if (resourceDirectoryTable.NumberOfNameEntries > 0) + { + resourceDirectoryTable.NameEntries = new ResourceDirectoryEntry[resourceDirectoryTable.NumberOfNameEntries]; + for (int i = 0; i < resourceDirectoryTable.NumberOfNameEntries; i++) + { + var entry = new ResourceDirectoryEntry(); + entry.NameOffset = data.ReadUInt32(); + + uint offset = data.ReadUInt32(); + if ((offset & 0xF0000000) != 0) + entry.SubdirectoryOffset = offset & ~0xF0000000; + else + entry.DataEntryOffset = offset; + + // Read the name from the offset + long currentOffset = data.Position; + offset = entry.NameOffset + (uint)initialOffset; + data.Seek(offset, SeekOrigin.Begin); + var resourceDirectoryString = new ResourceDirectoryString(); + resourceDirectoryString.Length = data.ReadUInt16(); + resourceDirectoryString.UnicodeString = data.ReadBytes(resourceDirectoryString.Length); + entry.Name = resourceDirectoryString; + data.Seek(currentOffset, SeekOrigin.Begin); + + resourceDirectoryTable.NameEntries[i] = entry; + } + } + if (resourceDirectoryTable.NumberOfIDEntries > 0) + { + resourceDirectoryTable.IDEntries = new ResourceDirectoryEntry[resourceDirectoryTable.NumberOfIDEntries]; + for (int i = 0; i < resourceDirectoryTable.NumberOfIDEntries; i++) + { + var entry = new ResourceDirectoryEntry(); + entry.IntegerID = data.ReadUInt32(); + + uint offset = data.ReadUInt32(); + if ((offset & 0xF0000000) != 0) + entry.SubdirectoryOffset = offset & ~0xF0000000; + else + entry.DataEntryOffset = offset; + + resourceDirectoryTable.IDEntries[i] = entry; + } + } + + // Read all leaves at this level + if (resourceDirectoryTable.NumberOfNameEntries > 0) + { + foreach (var entry in resourceDirectoryTable.NameEntries) + { + if (entry.SubdirectoryOffset != 0) + continue; + + uint offset = entry.DataEntryOffset + (uint)initialOffset; + data.Seek(offset, SeekOrigin.Begin); + + var resourceDataEntry = new ResourceDataEntry(); + resourceDataEntry.DataRVA = data.ReadUInt32(); + resourceDataEntry.Size = data.ReadUInt32(); + resourceDataEntry.Codepage = data.ReadUInt32(); + resourceDataEntry.Reserved = data.ReadUInt32(); + + // Read the data from the offset + offset = resourceDataEntry.DataRVA.ConvertVirtualAddress(sections); + if (offset > 0) + { + data.Seek(offset, SeekOrigin.Begin); + resourceDataEntry.Data = data.ReadBytes((int)resourceDataEntry.Size); + } + + entry.DataEntry = resourceDataEntry; + } + } + if (resourceDirectoryTable.NumberOfIDEntries > 0) + { + foreach (var entry in resourceDirectoryTable.IDEntries) + { + if (entry.SubdirectoryOffset != 0) + continue; + + uint offset = entry.DataEntryOffset + (uint)initialOffset; + data.Seek(offset, SeekOrigin.Begin); + + var resourceDataEntry = new ResourceDataEntry(); + resourceDataEntry.DataRVA = data.ReadUInt32(); + resourceDataEntry.Size = data.ReadUInt32(); + resourceDataEntry.Codepage = data.ReadUInt32(); + resourceDataEntry.Reserved = data.ReadUInt32(); + + // Read the data from the offset + offset = resourceDataEntry.DataRVA.ConvertVirtualAddress(sections); + if (offset > 0) + { + data.Seek(offset, SeekOrigin.Begin); + resourceDataEntry.Data = data.ReadBytes((int)resourceDataEntry.Size); + } + + entry.DataEntry = resourceDataEntry; + } + } + + // Now go one level lower + if (resourceDirectoryTable.NumberOfNameEntries > 0) + { + foreach (var entry in resourceDirectoryTable.NameEntries) + { + if (entry.DataEntryOffset != 0) + continue; + + uint offset = entry.SubdirectoryOffset + (uint)initialOffset; + data.Seek(offset, SeekOrigin.Begin); + entry.Subdirectory = ParseResourceDirectoryTable(data, initialOffset, sections); + } + } + if (resourceDirectoryTable.NumberOfIDEntries > 0) + { + foreach (var entry in resourceDirectoryTable.IDEntries) + { + if (entry.DataEntryOffset != 0) + continue; + + uint offset = entry.SubdirectoryOffset + (uint)initialOffset; + data.Seek(offset, SeekOrigin.Begin); + entry.Subdirectory = ParseResourceDirectoryTable(data, initialOffset, sections); + } + } + + return resourceDirectoryTable; + } + #endregion } } \ No newline at end of file diff --git a/BurnOutSharp.Models/PortableExecutable/Enums.cs b/BurnOutSharp.Models/PortableExecutable/Enums.cs index cfa81fd6..42717c3a 100644 --- a/BurnOutSharp.Models/PortableExecutable/Enums.cs +++ b/BurnOutSharp.Models/PortableExecutable/Enums.cs @@ -1693,6 +1693,30 @@ namespace BurnOutSharp.Models.PortableExecutable #endregion } + public enum ResourceType : uint + { + RT_NEWRESOURCE = 0x2000, + RT_ERROR = 0x7FFF, + + RT_CURSOR = 1, + RT_BITMAP = 2, + RT_ICON = 3, + RT_MENU = 4, + RT_DIALOG = 5, + RT_STRING = 6, + RT_FONTDIR = 7, + RT_FONT = 8, + RT_ACCELERATORS = 9, + RT_RCDATA = 10, + RT_MESSAGETABLE = 11, + RT_GROUP_CURSOR = 12, + RT_GROUP_ICON = 14, + RT_VERSION = 16, + RT_NEWBITMAP = (RT_BITMAP | RT_NEWRESOURCE), + RT_NEWMENU = (RT_MENU | RT_NEWRESOURCE), + RT_NEWDIALOG = (RT_DIALOG | RT_NEWRESOURCE), + } + [Flags] public enum SectionFlags : uint { diff --git a/BurnOutSharp.Models/PortableExecutable/Executable.cs b/BurnOutSharp.Models/PortableExecutable/Executable.cs index be6d31a5..a7182fff 100644 --- a/BurnOutSharp.Models/PortableExecutable/Executable.cs +++ b/BurnOutSharp.Models/PortableExecutable/Executable.cs @@ -56,7 +56,14 @@ namespace BurnOutSharp.Models.PortableExecutable /// public DelayLoadDirectoryTableEntry[] DelayLoadDirectoryTable { get; set; } - // TODO: Left off at "The .cormeta Section (Object Only)" + #region Named Sections + + /// + /// Resource directory table (.rsrc) + /// + public ResourceDirectoryTable ResourceDirectoryTable { get; set; } + + #endregion // TODO: Implement and/or document the following non-modeled parts: // - Grouped Sections (Object Only) diff --git a/BurnOutSharp.Models/PortableExecutable/ResourceDataEntry.cs b/BurnOutSharp.Models/PortableExecutable/ResourceDataEntry.cs index 789b3c86..984a8420 100644 --- a/BurnOutSharp.Models/PortableExecutable/ResourceDataEntry.cs +++ b/BurnOutSharp.Models/PortableExecutable/ResourceDataEntry.cs @@ -24,6 +24,11 @@ namespace BurnOutSharp.Models.PortableExecutable /// public uint Size; + /// + /// The resource data that is pointed to by the Data RVA field. + /// + public byte[] Data; + /// /// The code page that is used to decode code point values within the /// resource data. Typically, the code page would be the Unicode code page. diff --git a/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryEntry.cs b/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryEntry.cs index 7d2e27c8..7ca0129c 100644 --- a/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryEntry.cs +++ b/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryEntry.cs @@ -1,6 +1,4 @@ -using System.Runtime.InteropServices; - -namespace BurnOutSharp.Models.PortableExecutable +namespace BurnOutSharp.Models.PortableExecutable { /// /// A leaf's Type, Name, and Language IDs are determined by the path that is @@ -19,29 +17,52 @@ namespace BurnOutSharp.Models.PortableExecutable /// IMAGE_DIRECTORY_ENTRY_RESOURCE DataDirectory. /// /// - [StructLayout(LayoutKind.Explicit)] public class ResourceDirectoryEntry { + #region Offset 0x00 + /// /// The offset of a string that gives the Type, Name, or Language ID entry, /// depending on level of table. /// - [FieldOffset(0)] public uint NameOffset; + public uint NameOffset; + + /// + /// A string that gives the Type, Name, or Language ID entry, depending on + /// level of table. + /// + public ResourceDirectoryString Name; /// /// A 32-bit integer that identifies the Type, Name, or Language ID entry. /// - [FieldOffset(0)] public uint IntegerID; + public uint IntegerID; + + #endregion + + #region Offset 0x04 /// /// High bit 0. Address of a Resource Data entry (a leaf). /// - [FieldOffset(4)] public uint DataEntryOffset; + public uint DataEntryOffset; + + /// + /// Resource data entry (a leaf). + /// + public ResourceDataEntry DataEntry; /// /// High bit 1. The lower 31 bits are the address of another resource /// directory table (the next level down). /// - [FieldOffset(4)] public uint SubdirectoryOffset; + public uint SubdirectoryOffset; + + /// + /// Another resource directory table (the next level down). + /// + public ResourceDirectoryTable Subdirectory; + + #endregion } } diff --git a/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryTable.cs b/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryTable.cs index d1351636..5e412e5c 100644 --- a/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryTable.cs +++ b/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryTable.cs @@ -51,5 +51,18 @@ namespace BurnOutSharp.Models.PortableExecutable /// use numeric IDs for Type, Name, or Language entries. /// public ushort NumberOfIDEntries; + + /// + /// Directory entries immediately following the table that use + /// strings to identify Type, Name, or Language entries (depending on the + /// level of the table). + /// + public ResourceDirectoryEntry[] NameEntries; + + /// + /// Directory entries immediately following the Name entries that + /// use numeric IDs for Type, Name, or Language entries. + /// + public ResourceDirectoryEntry[] IDEntries; } }