mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-04-28 17:37:20 +00:00
Add PE base relocation table parsing and printing
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user