From 9be4b339f80c7fa024094762412a6a68aa2213fe Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sun, 4 Dec 2022 22:32:41 -0800 Subject: [PATCH] Add PE base relocation table parsing and printing --- BurnOutSharp.Builder/PortableExecutable.cs | 215 ++++++++++++++---- .../PortableExecutable/BaseRelocationBlock.cs | 2 +- .../BaseRelocationTypeOffsetFieldEntry.cs | 22 ++ .../PortableExecutable/Executable.cs | 6 +- BurnOutSharp.Wrappers/PortableExecutable.cs | 46 ++++ 5 files changed, 242 insertions(+), 49 deletions(-) create mode 100644 BurnOutSharp.Models/PortableExecutable/BaseRelocationTypeOffsetFieldEntry.cs diff --git a/BurnOutSharp.Builder/PortableExecutable.cs b/BurnOutSharp.Builder/PortableExecutable.cs index ed8342ab..b8b28179 100644 --- a/BurnOutSharp.Builder/PortableExecutable.cs +++ b/BurnOutSharp.Builder/PortableExecutable.cs @@ -175,6 +175,29 @@ namespace BurnOutSharp.Builder #endregion + #region Base Relocation Table + + // Should also be in a '.reloc' section + if (optionalHeader.BaseRelocationTable != null && optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0) + { + // If the offset for the base relocation table doesn't exist + int baseRelocationTableAddress = initialOffset + + (int)optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (baseRelocationTableAddress >= data.Length) + return executable; + + // Try to parse the base relocation table + int endOffset = (int)(baseRelocationTableAddress + optionalHeader.BaseRelocationTable.Size); + var baseRelocationTable = ParseBaseRelocationTable(data, baseRelocationTableAddress, endOffset, executable.SectionTable); + if (baseRelocationTable == null) + return null; + + // Set the base relocation table + executable.BaseRelocationTable = baseRelocationTable; + } + + #endregion + #region Debug Table // Should also be in a '.debug' section @@ -746,7 +769,49 @@ namespace BurnOutSharp.Builder } /// - /// Parse a Stream into a debug table + /// Parse a byte array into a base relocation table + /// + /// Byte array to parse + /// Offset into the byte array + /// First address not part of the base relocation table + /// Section table to use for virtual address translation + /// Filled base relocation table on success, null on error + private static BaseRelocationBlock[] ParseBaseRelocationTable(byte[] data, int offset, int endOffset, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var baseRelocationTable = new List(); + + while (offset < endOffset) + { + var baseRelocationBlock = new BaseRelocationBlock(); + + baseRelocationBlock.PageRVA = data.ReadUInt32(ref offset); + baseRelocationBlock.BlockSize = data.ReadUInt32(ref offset); + + var typeOffsetFieldEntries = new List(); + int totalSize = 8; + while (totalSize < baseRelocationBlock.BlockSize) + { + var baseRelocationTypeOffsetFieldEntry = new BaseRelocationTypeOffsetFieldEntry(); + + ushort typeAndOffsetField = data.ReadUInt16(ref offset); + baseRelocationTypeOffsetFieldEntry.BaseRelocationType = (BaseRelocationTypes)(typeAndOffsetField >> 12); + baseRelocationTypeOffsetFieldEntry.Offset = (ushort)(typeAndOffsetField & 0x0FFF); + + typeOffsetFieldEntries.Add(baseRelocationTypeOffsetFieldEntry); + totalSize += 2; + } + + baseRelocationBlock.TypeOffsetFieldEntries = typeOffsetFieldEntries.ToArray(); + + baseRelocationTable.Add(baseRelocationBlock); + } + + return baseRelocationTable.ToArray(); + } + + /// + /// Parse a byte array into a debug table /// /// Byte array to parse /// Offset into the byte array @@ -1329,6 +1394,30 @@ namespace BurnOutSharp.Builder #endregion + #region Base Relocation Table + + // Should also be in a '.reloc' section + if (optionalHeader.BaseRelocationTable != null && optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0) + { + // If the offset for the base relocation table doesn't exist + int baseRelocationTableAddress = initialOffset + + (int)optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (baseRelocationTableAddress >= data.Length) + return executable; + + // Try to parse the base relocation table + data.Seek(baseRelocationTableAddress, SeekOrigin.Begin); + int endOffset = (int)(baseRelocationTableAddress + optionalHeader.BaseRelocationTable.Size); + var baseRelocationTable = ParseBaseRelocationTable(data, endOffset, executable.SectionTable); + if (baseRelocationTable == null) + return null; + + // Set the base relocation table + executable.BaseRelocationTable = baseRelocationTable; + } + + #endregion + #region Debug Table // Should also be in a '.debug' section @@ -1895,6 +1984,47 @@ namespace BurnOutSharp.Builder return delayLoadDirectoryTable; } + /// + /// Parse a Stream into a base relocation table + /// + /// Stream to parse + /// First address not part of the base relocation table + /// Section table to use for virtual address translation + /// Filled base relocation table on success, null on error + private static BaseRelocationBlock[] ParseBaseRelocationTable(Stream data, int endOffset, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var baseRelocationTable = new List(); + + while (data.Position < endOffset) + { + var baseRelocationBlock = new BaseRelocationBlock(); + + baseRelocationBlock.PageRVA = data.ReadUInt32(); + baseRelocationBlock.BlockSize = data.ReadUInt32(); + + var typeOffsetFieldEntries = new List(); + int totalSize = 8; + while (totalSize < baseRelocationBlock.BlockSize) + { + var baseRelocationTypeOffsetFieldEntry = new BaseRelocationTypeOffsetFieldEntry(); + + ushort typeAndOffsetField = data.ReadUInt16(); + baseRelocationTypeOffsetFieldEntry.BaseRelocationType = (BaseRelocationTypes)(typeAndOffsetField >> 12); + baseRelocationTypeOffsetFieldEntry.Offset = (ushort)(typeAndOffsetField & 0x0FFF); + + typeOffsetFieldEntries.Add(baseRelocationTypeOffsetFieldEntry); + totalSize += 2; + } + + baseRelocationBlock.TypeOffsetFieldEntries = typeOffsetFieldEntries.ToArray(); + + baseRelocationTable.Add(baseRelocationBlock); + } + + return baseRelocationTable.ToArray(); + } + /// /// Parse a Stream into a debug table /// @@ -2254,51 +2384,49 @@ namespace BurnOutSharp.Builder resourceDirectoryTable.NumberOfNameEntries = data.ReadUInt16(); resourceDirectoryTable.NumberOfIDEntries = data.ReadUInt16(); - // Perform top-level pass of data + // If we have no entries int totalEntryCount = resourceDirectoryTable.NumberOfNameEntries + resourceDirectoryTable.NumberOfIDEntries; - if (totalEntryCount > 0) + if (totalEntryCount == 0) + return resourceDirectoryTable; + + // Perform top-level pass of data + resourceDirectoryTable.Entries = new ResourceDirectoryEntry[totalEntryCount]; + for (int i = 0; i < totalEntryCount; i++) { - resourceDirectoryTable.Entries = new ResourceDirectoryEntry[totalEntryCount]; - for (int i = 0; i < totalEntryCount; i++) + var entry = new ResourceDirectoryEntry(); + uint offset = data.ReadUInt32(); + if ((offset & 0x80000000) != 0) + entry.NameOffset = offset & ~0x80000000; + else + entry.IntegerID = offset; + + offset = data.ReadUInt32(); + if ((offset & 0x80000000) != 0) + entry.SubdirectoryOffset = offset & ~0x80000000; + else + entry.DataEntryOffset = offset; + + // Read the name from the offset, if needed + if (entry.NameOffset != default) { - var entry = new ResourceDirectoryEntry(); - uint offset = data.ReadUInt32(); - if ((offset & 0x80000000) != 0) - entry.NameOffset = offset & ~0x80000000; - else - entry.IntegerID = offset; - - offset = data.ReadUInt32(); - if ((offset & 0x80000000) != 0) - entry.SubdirectoryOffset = offset & ~0x80000000; - else - entry.DataEntryOffset = offset; - - // Read the name from the offset, if needed - if (entry.NameOffset != default) - { - 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 * 2); - entry.Name = resourceDirectoryString; - data.Seek(currentOffset, SeekOrigin.Begin); - } - - resourceDirectoryTable.Entries[i] = entry; + 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 * 2); + entry.Name = resourceDirectoryString; + data.Seek(currentOffset, SeekOrigin.Begin); } + + resourceDirectoryTable.Entries[i] = entry; } - // Read all leaves at this level - if (totalEntryCount > 0) + // Loop through and process the entries + foreach (var entry in resourceDirectoryTable.Entries) { - foreach (var entry in resourceDirectoryTable.Entries) + if (entry.DataEntryOffset != 0) { - if (entry.SubdirectoryOffset != 0) - continue; - uint offset = entry.DataEntryOffset + (uint)initialOffset; data.Seek(offset, SeekOrigin.Begin); @@ -2318,18 +2446,11 @@ namespace BurnOutSharp.Builder entry.DataEntry = resourceDataEntry; } - } - - // Now go one level lower - if (totalEntryCount > 0) - { - foreach (var entry in resourceDirectoryTable.Entries) + else if (entry.SubdirectoryOffset != 0) { - if (entry.DataEntryOffset != 0) - continue; - uint offset = entry.SubdirectoryOffset + (uint)initialOffset; data.Seek(offset, SeekOrigin.Begin); + entry.Subdirectory = ParseResourceDirectoryTable(data, initialOffset, sections); } } diff --git a/BurnOutSharp.Models/PortableExecutable/BaseRelocationBlock.cs b/BurnOutSharp.Models/PortableExecutable/BaseRelocationBlock.cs index 0831d315..1f0b1099 100644 --- a/BurnOutSharp.Models/PortableExecutable/BaseRelocationBlock.cs +++ b/BurnOutSharp.Models/PortableExecutable/BaseRelocationBlock.cs @@ -46,6 +46,6 @@ /// in the Page RVA field for the block. This offset /// specifies where the base relocation is to be applied. /// - public ushort[] TypeOffsetFieldEntries; + public BaseRelocationTypeOffsetFieldEntry[] TypeOffsetFieldEntries; } } diff --git a/BurnOutSharp.Models/PortableExecutable/BaseRelocationTypeOffsetFieldEntry.cs b/BurnOutSharp.Models/PortableExecutable/BaseRelocationTypeOffsetFieldEntry.cs new file mode 100644 index 00000000..8d018c6a --- /dev/null +++ b/BurnOutSharp.Models/PortableExecutable/BaseRelocationTypeOffsetFieldEntry.cs @@ -0,0 +1,22 @@ +namespace BurnOutSharp.Models.PortableExecutable +{ + /// + /// Type or Offset field entry is a WORD (2 bytes). + /// + /// + public class BaseRelocationTypeOffsetFieldEntry + { + /// + /// Stored in the high 4 bits of the WORD, a value that indicates the type + /// of base relocation to be applied. For more information, see + /// + public BaseRelocationTypes BaseRelocationType; + + /// + /// Stored in the remaining 12 bits of the WORD, an offset from the starting + /// address that was specified in the Page RVA field for the block. This + /// offset specifies where the base relocation is to be applied. + /// + public ushort Offset; + } +} diff --git a/BurnOutSharp.Models/PortableExecutable/Executable.cs b/BurnOutSharp.Models/PortableExecutable/Executable.cs index b5eda76d..0beed375 100644 --- a/BurnOutSharp.Models/PortableExecutable/Executable.cs +++ b/BurnOutSharp.Models/PortableExecutable/Executable.cs @@ -68,6 +68,11 @@ namespace BurnOutSharp.Models.PortableExecutable // the object file contains managed code. The format of the metadata is not // documented, but can be handed to the CLR interfaces for handling metadata. + /// + /// Base relocation table (.reloc) + /// + public BaseRelocationBlock[] BaseRelocationTable { get; set; } + /// /// Debug table (.debug*) /// @@ -125,7 +130,6 @@ namespace BurnOutSharp.Models.PortableExecutable // - Delay Bound Import Address Table // - Delay Unload Import Address Table // - The .pdata Section [Multiple formats per entry] - // - The .reloc Section (Image Only) // - The .tls Section // - TLS Callback Functions // - [The Load Configuration Structure (Image Only)] diff --git a/BurnOutSharp.Wrappers/PortableExecutable.cs b/BurnOutSharp.Wrappers/PortableExecutable.cs index 4e7c6df0..af01b94f 100644 --- a/BurnOutSharp.Wrappers/PortableExecutable.cs +++ b/BurnOutSharp.Wrappers/PortableExecutable.cs @@ -791,6 +791,9 @@ namespace BurnOutSharp.Wrappers PrintCOFFSymbolTable(); PrintAttributeCertificateTable(); PrintDelayLoadDirectoryTable(); + + // Named Sections + PrintBaseRelocationTable(); PrintDebugTable(); PrintExportTable(); PrintImportTable(); @@ -1310,6 +1313,49 @@ namespace BurnOutSharp.Wrappers Console.WriteLine(); } + /// + /// Print base relocation table information + /// + private void PrintBaseRelocationTable() + { + Console.WriteLine(" Base Relocation Table Information:"); + Console.WriteLine(" -------------------------"); + if (_executable.OptionalHeader?.BaseRelocationTable == null + || _executable.OptionalHeader.BaseRelocationTable.VirtualAddress == 0 + || _executable.BaseRelocationTable == null) + { + Console.WriteLine(" No base relocation table items"); + } + else + { + for (int i = 0; i < _executable.BaseRelocationTable.Length; i++) + { + var baseRelocationTableEntry = _executable.BaseRelocationTable[i]; + Console.WriteLine($" Base Relocation Table Entry {i}"); + Console.WriteLine($" Page RVA: {baseRelocationTableEntry.PageRVA}"); + Console.WriteLine($" Block size: {baseRelocationTableEntry.BlockSize}"); + + Console.WriteLine($" Base Relocation Table {i} Type and Offset Information:"); + Console.WriteLine(" -------------------------"); + if (baseRelocationTableEntry.TypeOffsetFieldEntries == null || baseRelocationTableEntry.TypeOffsetFieldEntries.Length == 0) + { + Console.WriteLine(" No base relocation table type and offset entries"); + } + else + { + for (int j = 0; j < baseRelocationTableEntry.TypeOffsetFieldEntries.Length; j++) + { + var typeOffsetFieldEntry = baseRelocationTableEntry.TypeOffsetFieldEntries[j]; + Console.WriteLine($" Type and Offset Entry {j}"); + Console.WriteLine($" Type: {typeOffsetFieldEntry.BaseRelocationType}"); + Console.WriteLine($" Offset: {typeOffsetFieldEntry.Offset}"); + } + } + } + } + Console.WriteLine(); + } + /// /// Print debug table information ///