diff --git a/BurnOutSharp.Builder/PortableExecutable.cs b/BurnOutSharp.Builder/PortableExecutable.cs index eed564df..9e3d67c7 100644 --- a/BurnOutSharp.Builder/PortableExecutable.cs +++ b/BurnOutSharp.Builder/PortableExecutable.cs @@ -179,13 +179,13 @@ namespace BurnOutSharp.Builder if (optionalHeader.ExportTable != null && optionalHeader.ExportTable.VirtualAddress != 0) { // If the offset for the export table doesn't exist - int tableAddress = initialOffset + int exportTableAddress = initialOffset + (int)optionalHeader.ExportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); - if (tableAddress >= data.Length) + if (exportTableAddress >= data.Length) return executable; // Try to parse the export table - var exportTable = ParseExportTable(data, tableAddress, executable.SectionTable); + var exportTable = ParseExportTable(data, exportTableAddress, executable.SectionTable); if (exportTable == null) return null; @@ -195,6 +195,28 @@ namespace BurnOutSharp.Builder #endregion + #region Import Table + + // Should also be in the '.idata' section + if (optionalHeader.ImportTable != null && optionalHeader.ImportTable.VirtualAddress != 0) + { + // If the offset for the import table doesn't exist + int importTableAddress = initialOffset + + (int)optionalHeader.ImportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (importTableAddress >= data.Length) + return executable; + + // Try to parse the import table + var importTable = ParseImportTable(data, importTableAddress, optionalHeader.Magic, executable.SectionTable); + if (importTable == null) + return null; + + // Set the import table + executable.ImportTable = importTable; + } + + #endregion + #region Resource Directory Table // Should also be in the '.rsrc' section @@ -692,12 +714,12 @@ namespace BurnOutSharp.Builder } /// - /// Parse a byte array into a export directory table + /// Parse a byte array into a export 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 + /// Filled export table on success, null on error private static ExportTable ParseExportTable(byte[] data, int offset, SectionHeader[] sections) { // TODO: Use marshalling here instead of building @@ -798,6 +820,152 @@ namespace BurnOutSharp.Builder return exportTable; } + /// + /// Parse a byte array into a import table + /// + /// Byte array to parse + /// Offset into the byte array + /// Optional header magic number indicating PE32 or PE32+ + /// Section table to use for virtual address translation + /// Filled import table on success, null on error + private static ImportTable ParseImportTable(byte[] data, int offset, OptionalHeaderMagicNumber magic, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var importTable = new ImportTable(); + + // Import directory table + var importDirectoryTable = new List(); + + // Loop until the last item (all nulls) are found + while (true) + { + var importDirectoryTableEntry = new ImportDirectoryTableEntry(); + + importDirectoryTableEntry.ImportLookupTableRVA = data.ReadUInt32(ref offset); + importDirectoryTableEntry.TimeDateStamp = data.ReadUInt32(ref offset); + importDirectoryTableEntry.ForwarderChain = data.ReadUInt32(ref offset); + importDirectoryTableEntry.NameRVA = data.ReadUInt32(ref offset); + importDirectoryTableEntry.ImportAddressTableRVA = data.ReadUInt32(ref offset); + + importDirectoryTable.Add(importDirectoryTableEntry); + + // All zero values means the last entry + if (importDirectoryTableEntry.ImportLookupTableRVA == 0 + && importDirectoryTableEntry.TimeDateStamp == 0 + && importDirectoryTableEntry.ForwarderChain == 0 + && importDirectoryTableEntry.NameRVA == 0 + && importDirectoryTableEntry.ImportAddressTableRVA == 0) + break; + } + + importTable.ImportDirectoryTable = importDirectoryTable.ToArray(); + + // Names + for (int i = 0; i < importTable.ImportDirectoryTable.Length; i++) + { + var importDirectoryTableEntry = importTable.ImportDirectoryTable[i]; + if (importDirectoryTableEntry.NameRVA != 0) + { + int nameAddress = (int)importDirectoryTableEntry.NameRVA.ConvertVirtualAddress(sections); + string name = data.ReadString(ref nameAddress, System.Text.Encoding.ASCII); + importDirectoryTableEntry.Name = name; + } + } + + // Lookup tables + for (int i = 0; i < importTable.ImportDirectoryTable.Length; i++) + { + var importLookupTable = new Dictionary(); + + var importDirectoryTableEntry = importTable.ImportDirectoryTable[i]; + if (importDirectoryTableEntry.ImportLookupTableRVA != 0) + { + int tableAddress = (int)importDirectoryTableEntry.ImportLookupTableRVA.ConvertVirtualAddress(sections); + var entryLookupTable = new List(); + + while (true) + { + var entryLookupTableEntry = new ImportLookupTableEntry(); + + if (magic == OptionalHeaderMagicNumber.PE32) + { + uint entryValue = data.ReadUInt32(ref tableAddress); + entryLookupTableEntry.OrdinalNameFlag = (entryValue & 0x80000000) != 0; + if (entryLookupTableEntry.OrdinalNameFlag) + entryLookupTableEntry.OrdinalNumber = (ushort)(entryValue & ~0x80000000); + else + entryLookupTableEntry.HintNameTableRVA = (uint)(entryValue & ~0x80000000); + } + else if (magic == OptionalHeaderMagicNumber.PE32Plus) + { + ulong entryValue = data.ReadUInt64(ref tableAddress); + entryLookupTableEntry.OrdinalNameFlag = (entryValue & 0x8000000000000000) != 0; + if (entryLookupTableEntry.OrdinalNameFlag) + entryLookupTableEntry.OrdinalNumber = (ushort)(entryValue & ~0x8000000000000000); + else + entryLookupTableEntry.HintNameTableRVA = (uint)(entryValue & ~0x8000000000000000); + } + + entryLookupTable.Add(entryLookupTableEntry); + + // All zero values means the last entry + if (entryLookupTableEntry.OrdinalNameFlag == false + && entryLookupTableEntry.OrdinalNumber == 0 + && entryLookupTableEntry.HintNameTableRVA == 0) + break; + } + + importLookupTable[i] = entryLookupTable.ToArray(); + } + + importTable.ImportLookupTables = importLookupTable; + } + + // Address tables + for (int i = 0; i < importTable.ImportDirectoryTable.Length; i++) + { + var importLookupTable = new Dictionary(); + + var importDirectoryTableEntry = importTable.ImportDirectoryTable[i]; + if (importDirectoryTableEntry.ImportAddressTableRVA != 0) + { + int tableAddress = (int)importDirectoryTableEntry.ImportAddressTableRVA.ConvertVirtualAddress(sections); + var entryAddressTable = new List(); + + while (true) + { + var entryLookupTableEntry = new ImportAddressTableEntry(); + + if (magic == OptionalHeaderMagicNumber.PE32) + { + uint entryValue = data.ReadUInt32(ref tableAddress); + entryLookupTableEntry.Address_PE32 = entryValue; + } + else if (magic == OptionalHeaderMagicNumber.PE32Plus) + { + ulong entryValue = data.ReadUInt64(ref tableAddress); + entryLookupTableEntry.Address_PE32Plus = entryValue; + } + + entryAddressTable.Add(entryLookupTableEntry); + + // All zero values means the last entry + if (entryLookupTableEntry.Address_PE32 == 0 + && entryLookupTableEntry.Address_PE32Plus == 0) + break; + } + + importLookupTable[i] = entryAddressTable.ToArray(); + } + + importTable.ImportAddressTables = importLookupTable; + } + + // TODO: Figure out how to find the hint/name table + + return importTable; + } + /// /// Parse a byte array into a resource directory table /// @@ -1123,6 +1291,29 @@ namespace BurnOutSharp.Builder #endregion + #region Import Table + + // Should also be in the '.idata' section + if (optionalHeader.ImportTable != null && optionalHeader.ImportTable.VirtualAddress != 0) + { + // If the offset for the import table doesn't exist + int importTableAddress = initialOffset + + (int)optionalHeader.ImportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (importTableAddress >= data.Length) + return executable; + + // Try to parse the import table + data.Seek(importTableAddress, SeekOrigin.Begin); + var importTable = ParseImportTable(data, optionalHeader.Magic, executable.SectionTable); + if (importTable == null) + return null; + + // Set the import table + executable.ImportTable = importTable; + } + + #endregion + #region Resource Directory Table // Should also be in the '.rsrc' section @@ -1614,11 +1805,11 @@ namespace BurnOutSharp.Builder } /// - /// Parse a Stream into a export directory table + /// Parse a Stream into a export table /// /// Stream to parse /// Section table to use for virtual address translation - /// Filled export directory table on success, null on error + /// Filled export table on success, null on error private static ExportTable ParseExportTable(Stream data, SectionHeader[] sections) { // TODO: Use marshalling here instead of building @@ -1729,6 +1920,157 @@ namespace BurnOutSharp.Builder return exportTable; } + /// + /// Parse a Stream into a import table + /// + /// Stream to parse + /// Optional header magic number indicating PE32 or PE32+ + /// Section table to use for virtual address translation + /// Filled import table on success, null on error + private static ImportTable ParseImportTable(Stream data, OptionalHeaderMagicNumber magic, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var importTable = new ImportTable(); + + // Import directory table + var importDirectoryTable = new List(); + + // Loop until the last item (all nulls) are found + while (true) + { + var importDirectoryTableEntry = new ImportDirectoryTableEntry(); + + importDirectoryTableEntry.ImportLookupTableRVA = data.ReadUInt32(); + importDirectoryTableEntry.TimeDateStamp = data.ReadUInt32(); + importDirectoryTableEntry.ForwarderChain = data.ReadUInt32(); + importDirectoryTableEntry.NameRVA = data.ReadUInt32(); + importDirectoryTableEntry.ImportAddressTableRVA = data.ReadUInt32(); + + importDirectoryTable.Add(importDirectoryTableEntry); + + // All zero values means the last entry + if (importDirectoryTableEntry.ImportLookupTableRVA == 0 + && importDirectoryTableEntry.TimeDateStamp == 0 + && importDirectoryTableEntry.ForwarderChain == 0 + && importDirectoryTableEntry.NameRVA == 0 + && importDirectoryTableEntry.ImportAddressTableRVA == 0) + break; + } + + importTable.ImportDirectoryTable = importDirectoryTable.ToArray(); + + // Names + for (int i = 0; i < importTable.ImportDirectoryTable.Length; i++) + { + var importDirectoryTableEntry = importTable.ImportDirectoryTable[i]; + if (importDirectoryTableEntry.NameRVA != 0) + { + uint nameAddress = importDirectoryTableEntry.NameRVA.ConvertVirtualAddress(sections); + data.Seek(nameAddress, SeekOrigin.Begin); + + string name = data.ReadString(System.Text.Encoding.ASCII); + importDirectoryTableEntry.Name = name; + } + } + + // Lookup tables + for (int i = 0; i < importTable.ImportDirectoryTable.Length; i++) + { + var importLookupTable = new Dictionary(); + + var importDirectoryTableEntry = importTable.ImportDirectoryTable[i]; + if (importDirectoryTableEntry.ImportLookupTableRVA != 0) + { + uint tableAddress = importDirectoryTableEntry.ImportLookupTableRVA.ConvertVirtualAddress(sections); + data.Seek(tableAddress, SeekOrigin.Begin); + + var entryLookupTable = new List(); + + while (true) + { + var entryLookupTableEntry = new ImportLookupTableEntry(); + + if (magic == OptionalHeaderMagicNumber.PE32) + { + uint entryValue = data.ReadUInt32(); + entryLookupTableEntry.OrdinalNameFlag = (entryValue & 0x80000000) != 0; + if (entryLookupTableEntry.OrdinalNameFlag) + entryLookupTableEntry.OrdinalNumber = (ushort)(entryValue & ~0x80000000); + else + entryLookupTableEntry.HintNameTableRVA = (uint)(entryValue & ~0x80000000); + } + else if (magic == OptionalHeaderMagicNumber.PE32Plus) + { + ulong entryValue = data.ReadUInt64(); + entryLookupTableEntry.OrdinalNameFlag = (entryValue & 0x8000000000000000) != 0; + if (entryLookupTableEntry.OrdinalNameFlag) + entryLookupTableEntry.OrdinalNumber = (ushort)(entryValue & ~0x8000000000000000); + else + entryLookupTableEntry.HintNameTableRVA = (uint)(entryValue & ~0x8000000000000000); + } + + entryLookupTable.Add(entryLookupTableEntry); + + // All zero values means the last entry + if (entryLookupTableEntry.OrdinalNameFlag == false + && entryLookupTableEntry.OrdinalNumber == 0 + && entryLookupTableEntry.HintNameTableRVA == 0) + break; + } + + importLookupTable[i] = entryLookupTable.ToArray(); + } + + importTable.ImportLookupTables = importLookupTable; + } + + // Address tables + for (int i = 0; i < importTable.ImportDirectoryTable.Length; i++) + { + var importLookupTable = new Dictionary(); + + var importDirectoryTableEntry = importTable.ImportDirectoryTable[i]; + if (importDirectoryTableEntry.ImportAddressTableRVA != 0) + { + uint tableAddress = importDirectoryTableEntry.ImportAddressTableRVA.ConvertVirtualAddress(sections); + data.Seek(tableAddress, SeekOrigin.Begin); + + var entryAddressTable = new List(); + + while (true) + { + var entryLookupTableEntry = new ImportAddressTableEntry(); + + if (magic == OptionalHeaderMagicNumber.PE32) + { + uint entryValue = data.ReadUInt32(); + entryLookupTableEntry.Address_PE32 = entryValue; + } + else if (magic == OptionalHeaderMagicNumber.PE32Plus) + { + ulong entryValue = data.ReadUInt64(); + entryLookupTableEntry.Address_PE32Plus = entryValue; + } + + entryAddressTable.Add(entryLookupTableEntry); + + // All zero values means the last entry + if (entryLookupTableEntry.Address_PE32 == 0 + && entryLookupTableEntry.Address_PE32Plus == 0) + break; + } + + importLookupTable[i] = entryAddressTable.ToArray(); + } + + importTable.ImportAddressTables = importLookupTable; + } + + // TODO: Figure out how to find the hint/name table + + return importTable; + } + /// /// Parse a Stream into a resource directory table /// diff --git a/BurnOutSharp.Models/PortableExecutable/ImportDirectoryTableEntry.cs b/BurnOutSharp.Models/PortableExecutable/ImportDirectoryTableEntry.cs index 2c65d4aa..3278e284 100644 --- a/BurnOutSharp.Models/PortableExecutable/ImportDirectoryTableEntry.cs +++ b/BurnOutSharp.Models/PortableExecutable/ImportDirectoryTableEntry.cs @@ -39,10 +39,15 @@ namespace BurnOutSharp.Models.PortableExecutable /// public uint NameRVA; + /// + /// ASCII string that contains the name of the DLL. + /// + public string Name; + /// /// The RVA of the import address table. The contents of this table are identical /// to the contents of the import lookup table until the image is bound. /// - public uint ImportAddressTableRVAThunkTable; + public uint ImportAddressTableRVA; } } diff --git a/BurnOutSharp.Models/PortableExecutable/ImportTable.cs b/BurnOutSharp.Models/PortableExecutable/ImportTable.cs index 8cb88587..38d53b45 100644 --- a/BurnOutSharp.Models/PortableExecutable/ImportTable.cs +++ b/BurnOutSharp.Models/PortableExecutable/ImportTable.cs @@ -1,4 +1,6 @@ -namespace BurnOutSharp.Models.PortableExecutable +using System.Collections.Generic; + +namespace BurnOutSharp.Models.PortableExecutable { /// /// All image files that import symbols, including virtually all executable (EXE) files, @@ -27,13 +29,13 @@ /// An import lookup table is an array of 32-bit numbers for PE32 or an array of 64-bit /// numbers for PE32+. /// - public ImportLookupTableEntry[,] ImportLookupTable; + public Dictionary ImportLookupTables; /// /// These addresses are the actual memory addresses of the symbols, although technically - /// they are still called "virtual addresses. + /// they are still called "virtual addresses". /// - public ImportAddressTableEntry[,] ImportAddressTable; + public Dictionary ImportAddressTables; /// /// One hint/name table suffices for the entire import section.