From 326bae1c49fd9c96ed10095f77fe1eeb53b64b10 Mon Sep 17 00:00:00 2001 From: Lloyd Kinsella Date: Tue, 31 Jul 2018 14:03:04 +0100 Subject: [PATCH] Lots of work on Imports, still work in progress but making it as generic as possible so code isn't duplicated. --- .../Annotations/EnumAnnotations.cs | 2 + Src/Workshell.PE/DataDirectory.cs | 4 + Src/Workshell.PE/Utils.cs | 2 + src/Workshell.PE.Testbed/Program.cs | 4 +- .../Annotations/FieldAnnotations.cs | 2 + .../Imports/DelayedImportAddressTable.cs | 14 ++ .../Imports/DelayedImportAddressTableEntry.cs | 15 ++ .../Imports/DelayedImportAddressTables.cs | 46 ++++++ .../Content/Imports/DelayedImportDirectory.cs | 94 ++++++++++++ .../Imports/DelayedImportDirectoryEntry.cs | 69 +++++++++ .../Content/Imports/ImportAddressTable.cs | 14 ++ .../Content/Imports/ImportAddressTableBase.cs | 134 +++++++++++++++++ .../Imports/ImportAddressTableEntry.cs | 15 ++ .../Imports/ImportAddressTableEntryBase.cs | 82 +++++++++++ .../Content/Imports/ImportAddressTables.cs | 46 ++++++ .../Imports/ImportAddressTablesBase.cs | 136 ++++++++++++++++++ .../Content/Imports/ImportDirectory.cs | 94 ++++++++++++ .../Content/Imports/ImportDirectoryBase.cs | 60 ++++++++ .../Content/Imports/ImportDirectoryEntry.cs | 57 ++++++++ .../Imports/ImportDirectoryEntryBase.cs | 46 ++++++ .../Content/LoadConfigurationDirectory.cs | 2 +- .../Native/IMAGE_DELAY_IMPORT_DESCRIPTOR.cs | 20 +++ .../Native/IMAGE_IMPORT_DESCRIPTOR.cs | 17 +++ 23 files changed, 972 insertions(+), 3 deletions(-) create mode 100644 src/Workshell.PE/Content/Imports/DelayedImportAddressTable.cs create mode 100644 src/Workshell.PE/Content/Imports/DelayedImportAddressTableEntry.cs create mode 100644 src/Workshell.PE/Content/Imports/DelayedImportAddressTables.cs create mode 100644 src/Workshell.PE/Content/Imports/DelayedImportDirectory.cs create mode 100644 src/Workshell.PE/Content/Imports/DelayedImportDirectoryEntry.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportAddressTable.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportAddressTableBase.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportAddressTableEntry.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportAddressTableEntryBase.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportAddressTables.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportAddressTablesBase.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportDirectory.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportDirectoryBase.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportDirectoryEntry.cs create mode 100644 src/Workshell.PE/Content/Imports/ImportDirectoryEntryBase.cs create mode 100644 src/Workshell.PE/Native/IMAGE_DELAY_IMPORT_DESCRIPTOR.cs create mode 100644 src/Workshell.PE/Native/IMAGE_IMPORT_DESCRIPTOR.cs diff --git a/Src/Workshell.PE/Annotations/EnumAnnotations.cs b/Src/Workshell.PE/Annotations/EnumAnnotations.cs index 15a0945..8a9a1c7 100644 --- a/Src/Workshell.PE/Annotations/EnumAnnotations.cs +++ b/Src/Workshell.PE/Annotations/EnumAnnotations.cs @@ -57,7 +57,9 @@ public EnumAnnotations() var underlayingType = Enum.GetUnderlyingType(typeof(T)); + #pragma warning disable CS0618 // Type or member is obsolete TypeSize = Marshal.SizeOf(underlayingType); + #pragma warning restore CS0618 // Type or member is obsolete } #region Methods diff --git a/Src/Workshell.PE/DataDirectory.cs b/Src/Workshell.PE/DataDirectory.cs index b5e4553..c2dcfc7 100644 --- a/Src/Workshell.PE/DataDirectory.cs +++ b/Src/Workshell.PE/DataDirectory.cs @@ -103,6 +103,10 @@ public async Task GetContentAsync() return await RelocationTable.LoadAsync(_image).ConfigureAwait(false); case DataDirectoryType.ExportTable: return await ExportDirectory.LoadAsync(_image).ConfigureAwait(false); + case DataDirectoryType.ImportTable: + return await ImportDirectory.LoadAsync(_image).ConfigureAwait(false); + case DataDirectoryType.DelayImportDescriptor: + return await DelayedImportDirectory.LoadAsync(_image).ConfigureAwait(false); default: { var calc = _image.GetCalculator(); diff --git a/Src/Workshell.PE/Utils.cs b/Src/Workshell.PE/Utils.cs index 7b94161..a8fa9e3 100644 --- a/Src/Workshell.PE/Utils.cs +++ b/Src/Workshell.PE/Utils.cs @@ -22,7 +22,9 @@ public static T Read(byte[] bytes) where T : struct { Marshal.Copy(bytes,0,ptr,bytes.Length); + #pragma warning disable CS0618 // Type or member is obsolete T result = (T)Marshal.PtrToStructure(ptr,typeof(T)); + #pragma warning restore CS0618 // Type or member is obsolete return result; } diff --git a/src/Workshell.PE.Testbed/Program.cs b/src/Workshell.PE.Testbed/Program.cs index ab4e823..8bd60f0 100644 --- a/src/Workshell.PE.Testbed/Program.cs +++ b/src/Workshell.PE.Testbed/Program.cs @@ -17,10 +17,10 @@ static async Task RunAsync(string[] args) { //var image = await PortableExecutableImage.FromFileAsync(@"C:\Users\lkinsella\Downloads\IISCrypto.exe"); var image = await PortableExecutableImage.FromFileAsync(@"C:\Windows\System32\shell32.dll"); - var dataDirectory = image.NTHeaders.DataDirectories[DataDirectoryType.ExportTable]; + var dataDirectory = image.NTHeaders.DataDirectories[DataDirectoryType.DelayImportDescriptor]; var content = await dataDirectory.GetContentAsync().ConfigureAwait(false); - var exports = await Exports.GetAsync(image).ConfigureAwait(false); + var x = await ImportAddressTables.GetLookupTableAsync(image).ConfigureAwait(false); } } } diff --git a/src/Workshell.PE/Annotations/FieldAnnotations.cs b/src/Workshell.PE/Annotations/FieldAnnotations.cs index a87b064..4fd6b58 100644 --- a/src/Workshell.PE/Annotations/FieldAnnotations.cs +++ b/src/Workshell.PE/Annotations/FieldAnnotations.cs @@ -71,6 +71,7 @@ internal FieldAnnotations(object annotatedObject) var type = prop.PropertyType; var size = 0; + #pragma warning disable CS0618 // Type or member is obsolete if (type.IsArray) { size = Marshal.SizeOf(type.GetElementType()); @@ -79,6 +80,7 @@ internal FieldAnnotations(object annotatedObject) { size = Marshal.SizeOf(type); } + #pragma warning restore CS0618 // Type or member is obsolete var value = prop.GetValue(annotatedObject,null); var annotation = new FieldAnnotation(desc,attr.ArrayLength,attr.Flags,name,type,value,size); diff --git a/src/Workshell.PE/Content/Imports/DelayedImportAddressTable.cs b/src/Workshell.PE/Content/Imports/DelayedImportAddressTable.cs new file mode 100644 index 0000000..b5d0997 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/DelayedImportAddressTable.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Workshell.PE.Content +{ + public sealed class DelayedImportAddressTable : ImportAddressTableBase + { + internal DelayedImportAddressTable(PortableExecutableImage image, DelayedImportDirectoryEntry directoryEntry, uint tableRVA, ulong[] tableEntries) : base(image, directoryEntry, tableRVA, tableEntries, false) + { + } + } +} diff --git a/src/Workshell.PE/Content/Imports/DelayedImportAddressTableEntry.cs b/src/Workshell.PE/Content/Imports/DelayedImportAddressTableEntry.cs new file mode 100644 index 0000000..d535ac3 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/DelayedImportAddressTableEntry.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Workshell.PE.Extensions; + +namespace Workshell.PE.Content +{ + public sealed class DelayedImportAddressTableEntry : ImportAddressTableEntryBase + { + internal DelayedImportAddressTableEntry(PortableExecutableImage image, ulong offset, ulong value, uint address, ushort ordinal, bool isOrdinal) : base(image, offset, value, address, ordinal, isOrdinal, true) + { + } + } +} diff --git a/src/Workshell.PE/Content/Imports/DelayedImportAddressTables.cs b/src/Workshell.PE/Content/Imports/DelayedImportAddressTables.cs new file mode 100644 index 0000000..86ea83a --- /dev/null +++ b/src/Workshell.PE/Content/Imports/DelayedImportAddressTables.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Workshell.PE.Content +{ + public sealed class DelayedImportAddressTables : ImportAddressTablesBase + { + internal DelayedImportAddressTables(PortableExecutableImage image, DataDirectory directory, Location location, Tuple[] tables) : base(image, directory, location, tables) + { + } + + #region Static Methods + + public static async Task GetLookupTableAsync(PortableExecutableImage image, DelayedImportDirectory directory = null) + { + if (directory == null) + directory = await DelayedImportDirectory.LoadAsync(image).ConfigureAwait(false); + + var tables = await LoadAsync( + image, + directory, + entry => entry.DelayNameTable + ).ConfigureAwait(false); + + return tables; + } + + public static async Task GetAddressTableAsync(PortableExecutableImage image, DelayedImportDirectory directory = null) + { + if (directory == null) + directory = await DelayedImportDirectory.LoadAsync(image).ConfigureAwait(false); + + var tables = await LoadAsync( + image, + directory, + entry => entry.DelayAddressTable + ).ConfigureAwait(false); + + return tables; + } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/DelayedImportDirectory.cs b/src/Workshell.PE/Content/Imports/DelayedImportDirectory.cs new file mode 100644 index 0000000..b790f39 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/DelayedImportDirectory.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +using Workshell.PE.Extensions; +using Workshell.PE.Native; + +namespace Workshell.PE.Content +{ + public sealed class DelayedImportDirectory : ImportDirectoryBase + { + internal DelayedImportDirectory(PortableExecutableImage image, DataDirectory directory, Location location, DelayedImportDirectoryEntry[] entries) : base(image, directory, location, entries) + { + } + + #region Static Methods + + internal static async Task LoadAsync(PortableExecutableImage image) + { + if (!image.NTHeaders.DataDirectories.Exists(DataDirectoryType.DelayImportDescriptor)) + return null; + + var dataDirectory = image.NTHeaders.DataDirectories[DataDirectoryType.DelayImportDescriptor]; + + if (DataDirectory.IsNullOrEmpty(dataDirectory)) + return null; + + var calc = image.GetCalculator(); + var section = calc.RVAToSection(dataDirectory.VirtualAddress); + var fileOffset = calc.RVAToOffset(section, dataDirectory.VirtualAddress); + var stream = image.GetStream(); + + stream.Seek(fileOffset.ToInt64(), SeekOrigin.Begin); + + var size = Marshal.SizeOf(); + var descriptors = new List>(); + + try + { + ulong offset = 0; + + while (true) + { + var descriptor = await stream.ReadStructAsync(size).ConfigureAwait(false); + + if (descriptor.Name == 0 && descriptor.ModuleHandle == 0) + break; + + var tuple = new Tuple(offset, descriptor); + + offset += size.ToUInt32(); + + descriptors.Add(tuple); + } + } + catch (Exception ex) + { + throw new PortableExecutableImageException(image, "Could not read delay import descriptor from stream.", ex); + } + + var imageBase = image.NTHeaders.OptionalHeader.ImageBase; + var totalSize = (descriptors.Count + 1) * size; + var location = new Location(fileOffset, dataDirectory.VirtualAddress, imageBase + dataDirectory.VirtualAddress, totalSize.ToUInt32(), totalSize.ToUInt32(), section); + var entries = new DelayedImportDirectoryEntry[descriptors.Count]; + + for (var i = 0; i < descriptors.Count; i++) + { + try + { + var entryOffset = fileOffset + descriptors[i].Item1; + var entryRVA = calc.OffsetToRVA(entryOffset); + var entryVA = imageBase + entryRVA; + var entryLocation = new Location(calc, entryOffset, entryRVA, entryVA, size.ToUInt32(), size.ToUInt32()); + var name = await GetNameAsync(calc, stream, descriptors[i].Item2.Name).ConfigureAwait(false); + + entries[i] = new DelayedImportDirectoryEntry(image, entryLocation, descriptors[i].Item2, name); + } + catch (Exception ex) + { + throw new PortableExecutableImageException(image, "Could not read delay import library name from stream.", ex); + } + } + + var result = new DelayedImportDirectory(image, dataDirectory, location, entries); + + return result; + } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/DelayedImportDirectoryEntry.cs b/src/Workshell.PE/Content/Imports/DelayedImportDirectoryEntry.cs new file mode 100644 index 0000000..23b5a37 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/DelayedImportDirectoryEntry.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Workshell.PE.Annotations; +using Workshell.PE.Native; + +namespace Workshell.PE.Content +{ + public sealed class DelayedImportDirectoryEntry : ImportDirectoryEntryBase + { + private readonly string _name; + + internal DelayedImportDirectoryEntry(PortableExecutableImage image, Location location, IMAGE_DELAY_IMPORT_DESCRIPTOR descriptor, string name) : base(image, location, true) + { + _name = name; + + Attributes = descriptor.Attributes; + Name = descriptor.Name; + ModuleHandle = descriptor.ModuleHandle; + DelayAddressTable = descriptor.DelayAddressTable; + DelayNameTable = descriptor.DelayNameTable; + BoundDelayIAT = descriptor.BoundDelayIAT; + UnloadDelayIAT = descriptor.UnloadDelayIAT; + TimeDateStamp = descriptor.TimeDateStamp; + } + + #region Methods + + public DateTime GetTimeDateStamp() + { + return Utils.ConvertTimeDateStamp(TimeDateStamp); + } + + public string GetName() + { + return _name; + } + + #endregion + + #region Properties + + [FieldAnnotation("Attributes")] + public uint Attributes { get; } + + [FieldAnnotation("Name")] + public uint Name { get; } + + [FieldAnnotation("Module Handle")] + public uint ModuleHandle { get; } + + [FieldAnnotation("Delay Import Address Table")] + public uint DelayAddressTable { get; } + + [FieldAnnotation("Delay Import Hint/Name Table")] + public uint DelayNameTable { get; } + + [FieldAnnotation("Bound Delay Import Address Table")] + public uint BoundDelayIAT { get; } + + [FieldAnnotation("Unload Delay Import Address Table")] + public uint UnloadDelayIAT { get; } + + [FieldAnnotation("Date/Time Stamp")] + public uint TimeDateStamp { get; } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportAddressTable.cs b/src/Workshell.PE/Content/Imports/ImportAddressTable.cs new file mode 100644 index 0000000..2fc0394 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportAddressTable.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Workshell.PE.Content +{ + public sealed class ImportAddressTable : ImportAddressTableBase + { + internal ImportAddressTable(PortableExecutableImage image, ImportDirectoryEntry directoryEntry, uint tableRVA, ulong[] tableEntries) : base(image, directoryEntry, tableRVA, tableEntries, false) + { + } + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportAddressTableBase.cs b/src/Workshell.PE/Content/Imports/ImportAddressTableBase.cs new file mode 100644 index 0000000..e5a49f4 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportAddressTableBase.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +using Workshell.PE.Extensions; + +namespace Workshell.PE.Content +{ + public abstract class ImportAddressTableBase : ISupportsLocation, ISupportsBytes, IEnumerable + where TEntry : ImportAddressTableEntryBase + where TDirectoryEntry : ImportDirectoryEntryBase + { + private readonly PortableExecutableImage _image; + private readonly TEntry[] _entries; + + protected internal ImportAddressTableBase(PortableExecutableImage image, TDirectoryEntry directoryEntry, uint rva, ulong[] entries, bool isDelayed) + { + _image = image; + _entries = BuildEntries(image, rva, entries); + + var calc = image.GetCalculator(); + var imageBase = image.NTHeaders.OptionalHeader.ImageBase; + var va = imageBase + rva; + var offset = calc.RVAToOffset(rva); + var size = (entries.Length * (image.Is64Bit ? sizeof(ulong) : sizeof(uint))).ToUInt64(); + + DirectoryEntry = directoryEntry; + Location = new Location(calc, offset, rva, va, size, size); + Count = _entries.Length; + IsDelayed = isDelayed; + } + + #region Methods + + public byte[] GetBytes() + { + return GetBytesAsync().GetAwaiter().GetResult(); + } + + public async Task GetBytesAsync() + { + var stream = _image.GetStream(); + var buffer = await stream.ReadBytesAsync(Location).ConfigureAwait(false); + + return buffer; + } + + public IEnumerator GetEnumerator() + { + foreach (var entry in _entries) + yield return entry; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private TEntry[] BuildEntries(PortableExecutableImage image, uint rva, ulong[] entries) + { + var results = new TEntry[entries.Length]; + var calc = image.GetCalculator(); + var offset = calc.RVAToOffset(rva); + + var entryType = typeof(TEntry); + var ctors = entryType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var ctor = ctors.First(); // TODO: Should probably match it better somehow + + for (var i = 0; i < entries.Length; i++) + { + var addrOrOrd = entries[i]; + ushort ordinal = 0; + var isOrdinal = false; + + if (!image.Is64Bit) + { + var value = addrOrOrd.ToUInt32(); + + if ((value & 0x80000000) == 0x80000000) + { + value &= 0x7fffffff; + + ordinal = Convert.ToUInt16(value); + isOrdinal = true; + } + } + else + { + var value = addrOrOrd; + + if ((value & 0x8000000000000000) == 0x8000000000000000) + { + value &= 0x7fffffffffffffff; + + ordinal = Convert.ToUInt16(value); + isOrdinal = true; + } + } + + uint address; + + if (isOrdinal) + { + address = 0; + } + else + { + address = Utils.LoDWord(addrOrOrd); + } + + results[i] = (TEntry)ctor.Invoke(new object[] { image, offset, addrOrOrd, address, ordinal, isOrdinal }); + offset += Convert.ToUInt32(image.Is64Bit ? sizeof(ulong) : sizeof(uint)); + } + + return results; + } + + #endregion + + #region Properties + + public TDirectoryEntry DirectoryEntry { get; } + public Location Location { get; } + public int Count { get; } + public TEntry this[int index] => _entries[index]; + public bool IsDelayed { get; } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportAddressTableEntry.cs b/src/Workshell.PE/Content/Imports/ImportAddressTableEntry.cs new file mode 100644 index 0000000..9be3af1 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportAddressTableEntry.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Workshell.PE.Extensions; + +namespace Workshell.PE.Content +{ + public sealed class ImportAddressTableEntry : ImportAddressTableEntryBase + { + internal ImportAddressTableEntry(PortableExecutableImage image, ulong offset, ulong value, uint address, ushort ordinal, bool isOrdinal) : base(image, offset, value, address, ordinal, isOrdinal, false) + { + } + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportAddressTableEntryBase.cs b/src/Workshell.PE/Content/Imports/ImportAddressTableEntryBase.cs new file mode 100644 index 0000000..e9e0392 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportAddressTableEntryBase.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Workshell.PE.Extensions; + +namespace Workshell.PE.Content +{ + public abstract class ImportAddressTableEntryBase : ISupportsLocation, ISupportsBytes + { + private readonly PortableExecutableImage _image; + + protected ImportAddressTableEntryBase(PortableExecutableImage image, ulong offset, ulong value, uint address, ushort ordinal, + bool isOrdinal, bool isDelayed) + { + _image = image; + + var calc = image.GetCalculator(); + var rva = calc.OffsetToRVA(offset); + var va = image.NTHeaders.OptionalHeader.ImageBase + rva; + var size = (image.Is64Bit ? sizeof(ulong) : sizeof(uint)).ToUInt64(); + + Location = new Location(calc, offset, rva, va, size, size); + Value = value; + Address = address; + Ordinal = ordinal; + IsOrdinal = isOrdinal; + IsDelayed = isDelayed; + } + + #region Methods + + public override string ToString() + { + var result = $"File Offset: 0x{Location.FileOffset:X8}, "; + + if (!IsOrdinal) + { + if (Location.FileSize == sizeof(ulong)) + { + result += $"Address: 0x{Address:X16}"; + } + else + { + result = $"Address: 0x{Address:X8}"; + } + } + else + { + result = $"Ordinal: 0x{Ordinal:D4}"; + } + + return result; + } + + public byte[] GetBytes() + { + return GetBytesAsync().GetAwaiter().GetResult(); + } + + public async Task GetBytesAsync() + { + var stream = _image.GetStream(); + var buffer = await stream.ReadBytesAsync(Location).ConfigureAwait(false); + + return buffer; + } + + #endregion + + #region Properties + + public Location Location { get; } + public ulong Value { get; } + public uint Address { get; } + public ushort Ordinal { get; } + public bool IsOrdinal { get; } + public bool IsDelayed { get; } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportAddressTables.cs b/src/Workshell.PE/Content/Imports/ImportAddressTables.cs new file mode 100644 index 0000000..7786167 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportAddressTables.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Workshell.PE.Content +{ + public sealed class ImportAddressTables : ImportAddressTablesBase + { + internal ImportAddressTables(PortableExecutableImage image, DataDirectory directory, Location location, Tuple[] tables) : base(image, directory, location, tables) + { + } + + #region Static Methods + + public static async Task GetLookupTableAsync(PortableExecutableImage image, ImportDirectory directory = null) + { + if (directory == null) + directory = await ImportDirectory.LoadAsync(image).ConfigureAwait(false); + + var tables = await LoadAsync( + image, + directory, + entry => entry.OriginalFirstThunk + ).ConfigureAwait(false); + + return tables; + } + + public static async Task GetAddressTableAsync(PortableExecutableImage image, ImportDirectory directory = null) + { + if (directory == null) + directory = await ImportDirectory.LoadAsync(image).ConfigureAwait(false); + + var tables = await LoadAsync( + image, + directory, + entry => entry.FirstThunk + ).ConfigureAwait(false); + + return tables; + } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportAddressTablesBase.cs b/src/Workshell.PE/Content/Imports/ImportAddressTablesBase.cs new file mode 100644 index 0000000..edc4ffe --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportAddressTablesBase.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Workshell.PE.Extensions; + +namespace Workshell.PE.Content +{ + public abstract class ImportAddressTablesBase : DataContent, IEnumerable + where TTable : ImportAddressTableBase + where TEntry : ImportAddressTableEntryBase + where TDirectoryEntry : ImportDirectoryEntryBase + { + private readonly TTable[] _tables; + + protected internal ImportAddressTablesBase(PortableExecutableImage image, DataDirectory directory, Location location, Tuple[] tables) : base(image, directory, location) + { + _tables = new TTable[tables.Length]; + + var tableType = typeof(TTable); + var ctors = tableType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var ctor = ctors.First(); // TODO: Should probably match it better somehow + + for (var i = 0; i < tables.Length; i++) + _tables[i] = (TTable)ctor.Invoke(new object[] {image, tables[i].Item2, tables[i].Item1, tables[i].Item3}); + + Count = _tables.Length; + } + + #region Static Methods + + internal static async Task LoadAsync(PortableExecutableImage image, ImportDirectoryBase directory, Func thunkRVA) + where TStaticTable : ImportAddressTableBase + where TStaticEntry : ImportAddressTableEntryBase + where TStaticDirectoryEntry : ImportDirectoryEntryBase + where TTables : ImportAddressTablesBase + { + if (directory == null) + return null; + + var calc = image.GetCalculator(); + var stream = image.GetStream(); + var tuples = new List>(); + + foreach (var directoryEntry in directory) + { + var thunk = thunkRVA(directoryEntry); + + if (thunk == 0) + continue; + + var entries = new List(); + var offset = calc.RVAToOffset(thunk); + + stream.Seek(offset.ToInt64(), SeekOrigin.Begin); + + while (true) + { + ulong entry; + + if (!image.Is64Bit) + { + entry = await stream.ReadUInt32Async().ConfigureAwait(false); + } + else + { + entry = await stream.ReadUInt64Async().ConfigureAwait(false); + } + + entries.Add(entry); + + if (entry == 0) + break; + } + + var table = new Tuple(thunk, directoryEntry, entries.ToArray()); + + tuples.Add(table); + } + + var rva = 0u; + + if (tuples.Count > 0) + rva = tuples.MinBy(table => table.Item1).Item1; + + var imageBase = image.NTHeaders.OptionalHeader.ImageBase; + var va = imageBase + rva; + var fileOffset = calc.RVAToOffset(rva); + ulong fileSize = 0; + + foreach (var table in tuples) + { + var size = (table.Item3.Length * (!image.Is64Bit ? sizeof(uint) : sizeof(ulong))).ToUInt32(); + + fileSize += size; + } + + var section = calc.RVAToSection(rva); + var location = new Location(fileOffset, rva, va, fileSize, fileSize, section); + var tableType = typeof(TTables); + var ctors = tableType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var ctor = ctors.First(); // TODO: Should probably match it better somehow + var result = (TTables)ctor.Invoke(new object[] {image, directory.Directory, location, tuples.ToArray()}); + + return result; + } + + #endregion + + #region Methods + + public IEnumerator GetEnumerator() + { + foreach (var table in _tables) + yield return table; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Properties + + public int Count { get; } + public TTable this[int index] => _tables[index]; + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportDirectory.cs b/src/Workshell.PE/Content/Imports/ImportDirectory.cs new file mode 100644 index 0000000..fe6ffd6 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportDirectory.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +using Workshell.PE.Extensions; +using Workshell.PE.Native; + +namespace Workshell.PE.Content +{ + public sealed class ImportDirectory : ImportDirectoryBase + { + internal ImportDirectory(PortableExecutableImage image, DataDirectory directory, Location location, ImportDirectoryEntry[] entries) : base(image, directory, location, entries) + { + } + + #region Static Methods + + internal static async Task LoadAsync(PortableExecutableImage image) + { + if (!image.NTHeaders.DataDirectories.Exists(DataDirectoryType.ImportTable)) + return null; + + DataDirectory dataDirectory = image.NTHeaders.DataDirectories[DataDirectoryType.ImportTable]; + + if (DataDirectory.IsNullOrEmpty(dataDirectory)) + return null; + + var calc = image.GetCalculator(); + var section = calc.RVAToSection(dataDirectory.VirtualAddress); + var fileOffset = calc.RVAToOffset(section, dataDirectory.VirtualAddress); + var stream = image.GetStream(); + + stream.Seek(fileOffset.ToInt64(), SeekOrigin.Begin); + + var size = Marshal.SizeOf(); + var descriptors = new List>(); + + try + { + ulong offset = 0; + + while (true) + { + var descriptor = await stream.ReadStructAsync(size).ConfigureAwait(false); + + if (descriptor.OriginalFirstThunk == 0 && descriptor.FirstThunk == 0) + break; + + var tuple = new Tuple(offset, descriptor); + + offset += size.ToUInt32(); + + descriptors.Add(tuple); + } + } + catch (Exception ex) + { + throw new PortableExecutableImageException(image, "Could not read import descriptor from stream.", ex); + } + + var imageBase = image.NTHeaders.OptionalHeader.ImageBase; + var totalSize = (descriptors.Count + 1) * size; + var location = new Location(fileOffset, dataDirectory.VirtualAddress, imageBase + dataDirectory.VirtualAddress, totalSize.ToUInt32(), totalSize.ToUInt32(), section); + var entries = new ImportDirectoryEntry[descriptors.Count]; + + for (var i = 0; i < descriptors.Count; i++) + { + try + { + var entryOffset = fileOffset + descriptors[i].Item1; + var entryRVA = calc.OffsetToRVA(entryOffset); + var entryVA = imageBase + entryRVA; + var entryLocation = new Location(calc, entryOffset, entryRVA, entryVA, size.ToUInt32(), size.ToUInt32()); + var name = await GetNameAsync(calc, stream, descriptors[i].Item2.Name).ConfigureAwait(false); + + entries[i] = new ImportDirectoryEntry(image, entryLocation, descriptors[i].Item2, name); + } + catch (Exception ex) + { + throw new PortableExecutableImageException(image, "Could not read import library name from stream.", ex); + } + } + + var result = new ImportDirectory(image, dataDirectory, location, entries); + + return result; + } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportDirectoryBase.cs b/src/Workshell.PE/Content/Imports/ImportDirectoryBase.cs new file mode 100644 index 0000000..fdd8554 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportDirectoryBase.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +using Workshell.PE.Extensions; + +namespace Workshell.PE.Content +{ + public abstract class ImportDirectoryBase : DataContent, IEnumerable where T : ImportDirectoryEntryBase + { + private readonly T[] _entries; + + protected internal ImportDirectoryBase(PortableExecutableImage image, DataDirectory directory, Location location, T[] entries) : base(image, directory, location) + { + _entries = entries; + + Count = _entries.Length; + } + + #region Static Methods + + protected internal static async Task GetNameAsync(LocationCalculator calc, Stream stream, uint nameRVA) + { + var fileOffset = calc.RVAToOffset(nameRVA); + + stream.Seek(fileOffset.ToInt64(), SeekOrigin.Begin); + + var result = await stream.ReadStringAsync().ConfigureAwait(false); + + return result; + } + + #endregion + + #region Methods + + public IEnumerator GetEnumerator() + { + foreach(var entry in _entries) + yield return entry; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Properties + + public int Count { get; } + public T this[int index] => _entries[index]; + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportDirectoryEntry.cs b/src/Workshell.PE/Content/Imports/ImportDirectoryEntry.cs new file mode 100644 index 0000000..ace5fb0 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportDirectoryEntry.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Workshell.PE.Annotations; +using Workshell.PE.Native; + +namespace Workshell.PE.Content +{ + public sealed class ImportDirectoryEntry : ImportDirectoryEntryBase + { + private readonly string _name; + + internal ImportDirectoryEntry(PortableExecutableImage image, Location location, IMAGE_IMPORT_DESCRIPTOR descriptor, string name) : base(image, location, false) + { + _name = name; + + OriginalFirstThunk = descriptor.OriginalFirstThunk; + TimeDateStamp = descriptor.TimeDateStamp; + ForwarderChain = descriptor.ForwarderChain; + Name = descriptor.Name; + FirstThunk = descriptor.FirstThunk; + } + + #region Methods + + public DateTime GetTimeDateStamp() + { + return Utils.ConvertTimeDateStamp(TimeDateStamp); + } + + public string GetName() + { + return _name; + } + + #endregion + + #region Properties + + [FieldAnnotation("Original First Thunk")] + public uint OriginalFirstThunk { get; } + + [FieldAnnotation("Date/Time Stamp")] + public uint TimeDateStamp { get; } + + [FieldAnnotation("Forwarder Chain")] + public uint ForwarderChain { get; } + + [FieldAnnotation("Name")] + public uint Name { get; } + + [FieldAnnotation("First Thunk")] + public uint FirstThunk { get; } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Imports/ImportDirectoryEntryBase.cs b/src/Workshell.PE/Content/Imports/ImportDirectoryEntryBase.cs new file mode 100644 index 0000000..8c66ca1 --- /dev/null +++ b/src/Workshell.PE/Content/Imports/ImportDirectoryEntryBase.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Workshell.PE.Extensions; + +namespace Workshell.PE.Content +{ + + public abstract class ImportDirectoryEntryBase : ISupportsLocation, ISupportsBytes + { + private readonly PortableExecutableImage _image; + + protected internal ImportDirectoryEntryBase(PortableExecutableImage image, Location location, bool isDelayed) + { + _image = image; + + Location = location; + IsDelayed = isDelayed; + } + + #region Methods + + public byte[] GetBytes() + { + return GetBytesAsync().GetAwaiter().GetResult(); + } + + public async Task GetBytesAsync() + { + var stream = _image.GetStream(); + var buffer = await stream.ReadBytesAsync(Location).ConfigureAwait(false); + + return buffer; + } + + #endregion + + #region Properties + + public Location Location { get; } + public bool IsDelayed { get; } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/LoadConfigurationDirectory.cs b/src/Workshell.PE/Content/LoadConfigurationDirectory.cs index 832b81f..87ec107 100644 --- a/src/Workshell.PE/Content/LoadConfigurationDirectory.cs +++ b/src/Workshell.PE/Content/LoadConfigurationDirectory.cs @@ -99,7 +99,7 @@ public static async Task LoadAsync(PortableExecutabl } catch (Exception ex) { - throw new PortableExecutableImageException(image, "Could not load Load Configration Directory from stream."); + throw new PortableExecutableImageException(image, "Could not load Load Configration Directory from stream.", ex); } directory = new LoadConfigurationDirectory(image, dataDirectory, location, config); diff --git a/src/Workshell.PE/Native/IMAGE_DELAY_IMPORT_DESCRIPTOR.cs b/src/Workshell.PE/Native/IMAGE_DELAY_IMPORT_DESCRIPTOR.cs new file mode 100644 index 0000000..005d5c5 --- /dev/null +++ b/src/Workshell.PE/Native/IMAGE_DELAY_IMPORT_DESCRIPTOR.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Workshell.PE.Native +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct IMAGE_DELAY_IMPORT_DESCRIPTOR + { + public uint Attributes; + public uint Name; + public uint ModuleHandle; + public uint DelayAddressTable; + public uint DelayNameTable; + public uint BoundDelayIAT; + public uint UnloadDelayIAT; + public uint TimeDateStamp; + } +} diff --git a/src/Workshell.PE/Native/IMAGE_IMPORT_DESCRIPTOR.cs b/src/Workshell.PE/Native/IMAGE_IMPORT_DESCRIPTOR.cs new file mode 100644 index 0000000..f0347b4 --- /dev/null +++ b/src/Workshell.PE/Native/IMAGE_IMPORT_DESCRIPTOR.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Workshell.PE.Native +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct IMAGE_IMPORT_DESCRIPTOR + { + public uint OriginalFirstThunk; + public uint TimeDateStamp; + public uint ForwarderChain; + public uint Name; + public uint FirstThunk; + } +}