From 5ba2a31d7d5dcd58085093eca5b9212eb7ff6d87 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 9 Nov 2022 23:06:52 -0800 Subject: [PATCH] Add PE export table to builder --- BurnOutSharp.Builder/PortableExecutable.cs | 268 ++++++++++++++++++ .../PortableExecutable/Executable.cs | 9 +- .../ExportDirectoryTable.cs | 5 + .../PortableExecutable/ExportNameTable.cs | 2 +- .../PortableExecutable/ExportTable.cs | 53 ++++ 5 files changed, 334 insertions(+), 3 deletions(-) create mode 100644 BurnOutSharp.Models/PortableExecutable/ExportTable.cs diff --git a/BurnOutSharp.Builder/PortableExecutable.cs b/BurnOutSharp.Builder/PortableExecutable.cs index 92319770..6661b60f 100644 --- a/BurnOutSharp.Builder/PortableExecutable.cs +++ b/BurnOutSharp.Builder/PortableExecutable.cs @@ -173,6 +173,28 @@ namespace BurnOutSharp.Builder #endregion + #region Export Table + + // Should also be in the '.rsrc' section + if (optionalHeader.ExportTable != null && optionalHeader.ExportTable.VirtualAddress != 0) + { + // If the offset for the export table doesn't exist + int tableAddress = initialOffset + + (int)optionalHeader.ExportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (tableAddress >= data.Length) + return executable; + + // Try to parse the export table + var exportTable = ParseExportTable(data, tableAddress, executable.SectionTable); + if (exportTable == null) + return null; + + // Set the export table + executable.ExportTable = exportTable; + } + + #endregion + #region Resource Directory Table // Should also be in the '.rsrc' section @@ -669,6 +691,113 @@ namespace BurnOutSharp.Builder return delayLoadDirectoryTable; } + /// + /// Parse a byte array into a export directory table + /// + /// Byte array to parse + /// Offset into the byte array + /// Section table to use for virtual address translation + /// Filled export directory table on success, null on error + private static ExportTable ParseExportTable(byte[] data, int offset, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var exportTable = new ExportTable(); + + var exportDirectoryTable = new ExportDirectoryTable(); + + exportDirectoryTable.ExportFlags = data.ReadUInt32(ref offset); + exportDirectoryTable.TimeDateStamp = data.ReadUInt32(ref offset); + exportDirectoryTable.MajorVersion = data.ReadUInt16(ref offset); + exportDirectoryTable.MinorVersion = data.ReadUInt16(ref offset); + exportDirectoryTable.NameRVA = data.ReadUInt32(ref offset); + exportDirectoryTable.OrdinalBase = data.ReadUInt32(ref offset); + exportDirectoryTable.AddressTableEntries = data.ReadUInt32(ref offset); + exportDirectoryTable.NumberOfNamePointers = data.ReadUInt32(ref offset); + exportDirectoryTable.ExportAddressTableRVA = data.ReadUInt32(ref offset); + exportDirectoryTable.NamePointerRVA = data.ReadUInt32(ref offset); + exportDirectoryTable.OrdinalTableRVA = data.ReadUInt32(ref offset); + + exportTable.ExportDirectoryTable = exportDirectoryTable; + + // Name + if (exportDirectoryTable.NameRVA != 0) + { + offset = (int)exportDirectoryTable.NameRVA.ConvertVirtualAddress(sections); + string name = data.ReadString(ref offset, System.Text.Encoding.ASCII); + exportDirectoryTable.Name = name; + } + + // Address table + if (exportDirectoryTable.AddressTableEntries != 0 && exportDirectoryTable.ExportAddressTableRVA != 0) + { + offset = (int)exportDirectoryTable.ExportAddressTableRVA.ConvertVirtualAddress(sections); + var exportAddressTable = new ExportAddressTableEntry[exportDirectoryTable.AddressTableEntries]; + + for (int i = 0; i < exportDirectoryTable.AddressTableEntries; i++) + { + var addressTableEntry = new ExportAddressTableEntry(); + + // TODO: Use the optional header address and length to determine if export or forwarder + addressTableEntry.ExportRVA = data.ReadUInt32(ref offset); + addressTableEntry.ForwarderRVA = addressTableEntry.ExportRVA; + + exportAddressTable[i] = addressTableEntry; + } + + exportTable.ExportAddressTable = exportAddressTable; + } + + // Name pointer table + if (exportDirectoryTable.NumberOfNamePointers != 0 && exportDirectoryTable.NamePointerRVA != 0) + { + offset = (int)exportDirectoryTable.NamePointerRVA.ConvertVirtualAddress(sections); + var namePointerTable = new ExportNamePointerTable(); + + namePointerTable.Pointers = new uint[exportDirectoryTable.NumberOfNamePointers]; + for (int i = 0; i < exportDirectoryTable.NumberOfNamePointers; i++) + { + uint pointer = data.ReadUInt32(ref offset); + namePointerTable.Pointers[i] = pointer; + } + + exportTable.NamePointerTable = namePointerTable; + } + + // Ordinal table + if (exportDirectoryTable.NumberOfNamePointers != 0 && exportDirectoryTable.OrdinalTableRVA != 0) + { + offset = (int)exportDirectoryTable.OrdinalTableRVA.ConvertVirtualAddress(sections); + var exportOrdinalTable = new ExportOrdinalTable(); + + exportOrdinalTable.Indexes = new ushort[exportDirectoryTable.NumberOfNamePointers]; + for (int i = 0; i < exportDirectoryTable.NumberOfNamePointers; i++) + { + ushort pointer = data.ReadUInt16(ref offset); + exportOrdinalTable.Indexes[i] = pointer; + } + + exportTable.OrdinalTable = exportOrdinalTable; + } + + // Name table + if (exportDirectoryTable.NumberOfNamePointers != 0 && exportDirectoryTable.NameRVA != 0) + { + offset = (int)exportDirectoryTable.NameRVA.ConvertVirtualAddress(sections); + var exportNameTable = new ExportNameTable(); + + exportNameTable.Strings = new string[exportDirectoryTable.NumberOfNamePointers]; + for (int i = 0; i < exportDirectoryTable.NumberOfNamePointers; i++) + { + string str = data.ReadString(ref offset, System.Text.Encoding.ASCII); + exportNameTable.Strings[i] = str; + } + + exportTable.ExportNameTable = exportNameTable; + } + + return exportTable; + } + /// /// Parse a byte array into a resource directory table /// @@ -971,6 +1100,29 @@ namespace BurnOutSharp.Builder #endregion + #region Export Table + + // Should also be in the '.edata' section + if (optionalHeader.ExportTable != null && optionalHeader.ExportTable.VirtualAddress != 0) + { + // If the offset for the export table doesn't exist + int exportTableAddress = initialOffset + + (int)optionalHeader.ExportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (exportTableAddress >= data.Length) + return executable; + + // Try to parse the export table + data.Seek(exportTableAddress, SeekOrigin.Begin); + var exportTable = ParseExportTable(data, executable.SectionTable); + if (exportTable == null) + return null; + + // Set the export table + executable.ExportTable = exportTable; + } + + #endregion + #region Resource Directory Table // Should also be in the '.rsrc' section @@ -1461,6 +1613,122 @@ namespace BurnOutSharp.Builder return delayLoadDirectoryTable; } + /// + /// Parse a Stream into a export directory table + /// + /// Stream to parse + /// Section table to use for virtual address translation + /// Filled export directory table on success, null on error + private static ExportTable ParseExportTable(Stream data, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var exportTable = new ExportTable(); + + var exportDirectoryTable = new ExportDirectoryTable(); + + exportDirectoryTable.ExportFlags = data.ReadUInt32(); + exportDirectoryTable.TimeDateStamp = data.ReadUInt32(); + exportDirectoryTable.MajorVersion = data.ReadUInt16(); + exportDirectoryTable.MinorVersion = data.ReadUInt16(); + exportDirectoryTable.NameRVA = data.ReadUInt32(); + exportDirectoryTable.OrdinalBase = data.ReadUInt32(); + exportDirectoryTable.AddressTableEntries = data.ReadUInt32(); + exportDirectoryTable.NumberOfNamePointers = data.ReadUInt32(); + exportDirectoryTable.ExportAddressTableRVA = data.ReadUInt32(); + exportDirectoryTable.NamePointerRVA = data.ReadUInt32(); + exportDirectoryTable.OrdinalTableRVA = data.ReadUInt32(); + + exportTable.ExportDirectoryTable = exportDirectoryTable; + + // Name + if (exportDirectoryTable.NameRVA != 0) + { + uint nameAddress = exportDirectoryTable.NameRVA.ConvertVirtualAddress(sections); + data.Seek(nameAddress, SeekOrigin.Begin); + + string name = data.ReadString(System.Text.Encoding.ASCII); + exportDirectoryTable.Name = name; + } + + // Address table + if (exportDirectoryTable.AddressTableEntries != 0 && exportDirectoryTable.ExportAddressTableRVA != 0) + { + uint exportAddressTableAddress = exportDirectoryTable.ExportAddressTableRVA.ConvertVirtualAddress(sections); + data.Seek(exportAddressTableAddress, SeekOrigin.Begin); + + var exportAddressTable = new ExportAddressTableEntry[exportDirectoryTable.AddressTableEntries]; + + for (int i = 0; i < exportDirectoryTable.AddressTableEntries; i++) + { + var addressTableEntry = new ExportAddressTableEntry(); + + // TODO: Use the optional header address and length to determine if export or forwarder + addressTableEntry.ExportRVA = data.ReadUInt32(); + addressTableEntry.ForwarderRVA = addressTableEntry.ExportRVA; + + exportAddressTable[i] = addressTableEntry; + } + + exportTable.ExportAddressTable = exportAddressTable; + } + + // Name pointer table + if (exportDirectoryTable.NumberOfNamePointers != 0 && exportDirectoryTable.NamePointerRVA != 0) + { + uint namePointerTableAddress = exportDirectoryTable.NamePointerRVA.ConvertVirtualAddress(sections); + data.Seek(namePointerTableAddress, SeekOrigin.Begin); + + var namePointerTable = new ExportNamePointerTable(); + + namePointerTable.Pointers = new uint[exportDirectoryTable.NumberOfNamePointers]; + for (int i = 0; i < exportDirectoryTable.NumberOfNamePointers; i++) + { + uint pointer = data.ReadUInt32(); + namePointerTable.Pointers[i] = pointer; + } + + exportTable.NamePointerTable = namePointerTable; + } + + // Ordinal table + if (exportDirectoryTable.NumberOfNamePointers != 0 && exportDirectoryTable.OrdinalTableRVA != 0) + { + uint ordinalTableAddress = exportDirectoryTable.OrdinalTableRVA.ConvertVirtualAddress(sections); + data.Seek(ordinalTableAddress, SeekOrigin.Begin); + + var exportOrdinalTable = new ExportOrdinalTable(); + + exportOrdinalTable.Indexes = new ushort[exportDirectoryTable.NumberOfNamePointers]; + for (int i = 0; i < exportDirectoryTable.NumberOfNamePointers; i++) + { + ushort pointer = data.ReadUInt16(); + exportOrdinalTable.Indexes[i] = pointer; + } + + exportTable.OrdinalTable = exportOrdinalTable; + } + + // Name table + if (exportDirectoryTable.NumberOfNamePointers != 0 && exportDirectoryTable.NameRVA != 0) + { + uint nameTableAddress = exportDirectoryTable.NameRVA.ConvertVirtualAddress(sections); + data.Seek(nameTableAddress, SeekOrigin.Begin); + + var exportNameTable = new ExportNameTable(); + + exportNameTable.Strings = new string[exportDirectoryTable.NumberOfNamePointers]; + for (int i = 0; i < exportDirectoryTable.NumberOfNamePointers; i++) + { + string str = data.ReadString(); + exportNameTable.Strings[i] = str; + } + + exportTable.ExportNameTable = exportNameTable; + } + + return exportTable; + } + /// /// Parse a Stream into a resource directory table /// diff --git a/BurnOutSharp.Models/PortableExecutable/Executable.cs b/BurnOutSharp.Models/PortableExecutable/Executable.cs index 77899a6e..e1ea7f68 100644 --- a/BurnOutSharp.Models/PortableExecutable/Executable.cs +++ b/BurnOutSharp.Models/PortableExecutable/Executable.cs @@ -64,6 +64,11 @@ namespace BurnOutSharp.Models.PortableExecutable #region Named Sections + /// + /// Export table (.edata); + /// + public ExportTable ExportTable { get; set; } + /// /// Resource directory table (.rsrc) /// @@ -86,8 +91,8 @@ namespace BurnOutSharp.Models.PortableExecutable // - [Export Ordinal Table] // - [Export Name Table] // - The .idata Section - // - Import Lookup Table [has model, but bit-based] - // - Import Address Table + // - Import Lookup Table [has model, but bit-based] + // - Import Address Table // - The .pdata Section [Multiple formats per entry] // - TLS Callback Functions // - The .cormeta Section (Object Only) diff --git a/BurnOutSharp.Models/PortableExecutable/ExportDirectoryTable.cs b/BurnOutSharp.Models/PortableExecutable/ExportDirectoryTable.cs index bca2ac26..427676bf 100644 --- a/BurnOutSharp.Models/PortableExecutable/ExportDirectoryTable.cs +++ b/BurnOutSharp.Models/PortableExecutable/ExportDirectoryTable.cs @@ -39,6 +39,11 @@ namespace BurnOutSharp.Models.PortableExecutable /// public uint NameRVA; + /// + /// ASCII string that contains the name of the DLL. + /// + public string Name; + /// /// The starting ordinal number for exports in this image. This field specifies /// the starting ordinal number for the export address table. It is usually set diff --git a/BurnOutSharp.Models/PortableExecutable/ExportNameTable.cs b/BurnOutSharp.Models/PortableExecutable/ExportNameTable.cs index 671f8a54..accf45d9 100644 --- a/BurnOutSharp.Models/PortableExecutable/ExportNameTable.cs +++ b/BurnOutSharp.Models/PortableExecutable/ExportNameTable.cs @@ -22,6 +22,6 @@ /// /// A series of null-terminated ASCII strings of variable length. /// - public string[] Indexes; + public string[] Strings; } } diff --git a/BurnOutSharp.Models/PortableExecutable/ExportTable.cs b/BurnOutSharp.Models/PortableExecutable/ExportTable.cs new file mode 100644 index 00000000..bdbd16ad --- /dev/null +++ b/BurnOutSharp.Models/PortableExecutable/ExportTable.cs @@ -0,0 +1,53 @@ +namespace BurnOutSharp.Models.PortableExecutable +{ + /// + /// The export data section, named .edata, contains information about symbols that other images + /// can access through dynamic linking. Exported symbols are generally found in DLLs, but DLLs + /// can also import symbols. + /// + /// An overview of the general structure of the export section is described below. The tables + /// described are usually contiguous in the file in the order shown (though this is not + /// required). Only the export directory table and export address table are required to export + /// symbols as ordinals. (An ordinal is an export that is accessed directly by its export + /// address table index.) The name pointer table, ordinal table, and export name table all + /// exist to support use of export names. + /// + /// + public class ExportTable + { + /// + /// A table with just one row (unlike the debug directory). This table indicates the + /// locations and sizes of the other export tables. + /// + public ExportDirectoryTable ExportDirectoryTable; + + /// + /// An array of RVAs of exported symbols. These are the actual addresses of the exported + /// functions and data within the executable code and data sections. Other image files + /// can import a symbol by using an index to this table (an ordinal) or, optionally, by + /// using the public name that corresponds to the ordinal if a public name is defined. + /// + public ExportAddressTableEntry[] ExportAddressTable; + + /// + /// An array of pointers to the public export names, sorted in ascending order. + /// + public ExportNamePointerTable NamePointerTable; + + /// + /// An array of the ordinals that correspond to members of the name pointer table. The + /// correspondence is by position; therefore, the name pointer table and the ordinal table + /// must have the same number of members. Each ordinal is an index into the export address + /// table. + /// + public ExportOrdinalTable OrdinalTable; + + /// + /// A series of null-terminated ASCII strings. Members of the name pointer table point into + /// this area. These names are the public names through which the symbols are imported and + /// exported; they are not necessarily the same as the private names that are used within + /// the image file. + /// + public ExportNameTable ExportNameTable; + } +}