Add PE base relocation table parsing and printing

This commit is contained in:
Matt Nadareski
2022-12-04 22:32:41 -08:00
parent ce1c74aec3
commit 9be4b339f8
5 changed files with 242 additions and 49 deletions

View File

@@ -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
}
/// <summary>
/// Parse a Stream into a debug table
/// Parse a byte array into a base relocation table
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <param name="endOffset">First address not part of the base relocation table</param>
/// <param name="sections">Section table to use for virtual address translation</param>
/// <returns>Filled base relocation table on success, null on error</returns>
private static BaseRelocationBlock[] ParseBaseRelocationTable(byte[] data, int offset, int endOffset, SectionHeader[] sections)
{
// TODO: Use marshalling here instead of building
var baseRelocationTable = new List<BaseRelocationBlock>();
while (offset < endOffset)
{
var baseRelocationBlock = new BaseRelocationBlock();
baseRelocationBlock.PageRVA = data.ReadUInt32(ref offset);
baseRelocationBlock.BlockSize = data.ReadUInt32(ref offset);
var typeOffsetFieldEntries = new List<BaseRelocationTypeOffsetFieldEntry>();
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();
}
/// <summary>
/// Parse a byte array into a debug table
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
@@ -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;
}
/// <summary>
/// Parse a Stream into a base relocation table
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="endOffset">First address not part of the base relocation table</param>
/// <param name="sections">Section table to use for virtual address translation</param>
/// <returns>Filled base relocation table on success, null on error</returns>
private static BaseRelocationBlock[] ParseBaseRelocationTable(Stream data, int endOffset, SectionHeader[] sections)
{
// TODO: Use marshalling here instead of building
var baseRelocationTable = new List<BaseRelocationBlock>();
while (data.Position < endOffset)
{
var baseRelocationBlock = new BaseRelocationBlock();
baseRelocationBlock.PageRVA = data.ReadUInt32();
baseRelocationBlock.BlockSize = data.ReadUInt32();
var typeOffsetFieldEntries = new List<BaseRelocationTypeOffsetFieldEntry>();
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();
}
/// <summary>
/// Parse a Stream into a debug table
/// </summary>
@@ -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);
}
}

View File

@@ -46,6 +46,6 @@
/// in the Page RVA field for the block. This offset
/// specifies where the base relocation is to be applied.
/// </summary>
public ushort[] TypeOffsetFieldEntries;
public BaseRelocationTypeOffsetFieldEntry[] TypeOffsetFieldEntries;
}
}

View File

@@ -0,0 +1,22 @@
namespace BurnOutSharp.Models.PortableExecutable
{
/// <summary>
/// Type or Offset field entry is a WORD (2 bytes).
/// </summary>
/// <see href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format"/>
public class BaseRelocationTypeOffsetFieldEntry
{
/// <summary>
/// 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 <see cref="BaseRelocationTypes"/>
/// </summary>
public BaseRelocationTypes BaseRelocationType;
/// <summary>
/// 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.
/// </summary>
public ushort Offset;
}
}

View File

@@ -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.
/// <summary>
/// Base relocation table (.reloc)
/// </summary>
public BaseRelocationBlock[] BaseRelocationTable { get; set; }
/// <summary>
/// Debug table (.debug*)
/// </summary>
@@ -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)]

View File

@@ -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();
}
/// <summary>
/// Print base relocation table information
/// </summary>
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();
}
/// <summary>
/// Print debug table information
/// </summary>