From 857fd55b1adc1feb15fbd118a268522cd728ad55 Mon Sep 17 00:00:00 2001 From: Lloyd Kinsella Date: Wed, 5 Sep 2018 14:00:15 +0100 Subject: [PATCH] More work on porting various resources. --- .../Menus/MenuResource.cs | 2 +- .../Messages/MessageTable.cs | 46 ++++++++ .../Messages/MessageTableBlock.cs | 48 ++++++++ .../Messages/MessageTableEntry.cs | 33 ++++++ .../Messages/MessageTableResource.cs | 98 ++++++++++++++++ .../Native/MESSAGETABLE.cs | 28 +++++ .../Strings/StringTable.cs | 46 ++++++++ .../Strings/StringTableEntry.cs | 35 ++++++ .../Strings/StringTableResource.cs | 109 ++++++++++++++++++ src/Workshell.PE.Testbed/Program.cs | 12 +- 10 files changed, 452 insertions(+), 5 deletions(-) create mode 100644 src/Workshell.PE.Resources/Messages/MessageTable.cs create mode 100644 src/Workshell.PE.Resources/Messages/MessageTableBlock.cs create mode 100644 src/Workshell.PE.Resources/Messages/MessageTableEntry.cs create mode 100644 src/Workshell.PE.Resources/Messages/MessageTableResource.cs create mode 100644 src/Workshell.PE.Resources/Native/MESSAGETABLE.cs create mode 100644 src/Workshell.PE.Resources/Strings/StringTable.cs create mode 100644 src/Workshell.PE.Resources/Strings/StringTableEntry.cs create mode 100644 src/Workshell.PE.Resources/Strings/StringTableResource.cs diff --git a/src/Workshell.PE.Resources/Menus/MenuResource.cs b/src/Workshell.PE.Resources/Menus/MenuResource.cs index d5aecdc..b8476c5 100644 --- a/src/Workshell.PE.Resources/Menus/MenuResource.cs +++ b/src/Workshell.PE.Resources/Menus/MenuResource.cs @@ -64,7 +64,7 @@ public async Task GetMenuAsync(ResourceLanguage language) } catch (Exception ex) { - throw new PortableExecutableImageException(Image, "Could not read dialog from stream.", ex); + throw new PortableExecutableImageException(Image, "Could not read menu from stream.", ex); } } } diff --git a/src/Workshell.PE.Resources/Messages/MessageTable.cs b/src/Workshell.PE.Resources/Messages/MessageTable.cs new file mode 100644 index 0000000..7f24dc0 --- /dev/null +++ b/src/Workshell.PE.Resources/Messages/MessageTable.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Workshell.PE.Resources.Messages +{ + public sealed class MessageTable : IEnumerable + { + private readonly MessageTableBlock[] _blocks; + + internal MessageTable(MessageTableResource resource, uint languageId, MessageTableBlock[] blocks) + { + _blocks = blocks; + + Resource = resource; + Language = languageId; + Count = _blocks.Length; + } + + #region Methods + + public IEnumerator GetEnumerator() + { + foreach (var block in _blocks) + yield return block; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Properties + + public MessageTableResource Resource { get; } + public ResourceLanguage Language { get; } + + public int Count { get; } + public MessageTableBlock this[int index] => _blocks[index]; + + #endregion + } +} diff --git a/src/Workshell.PE.Resources/Messages/MessageTableBlock.cs b/src/Workshell.PE.Resources/Messages/MessageTableBlock.cs new file mode 100644 index 0000000..97b0c07 --- /dev/null +++ b/src/Workshell.PE.Resources/Messages/MessageTableBlock.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Workshell.PE.Resources.Messages +{ + public sealed class MessageTableBlock : IEnumerable + { + private readonly MessageTableEntry[] _entries; + + internal MessageTableBlock(uint lowId, uint highId, uint offsetToEntries, MessageTableEntry[] entries) + { + _entries = entries; + + LowId = lowId; + HighId = highId; + OffsetToEntries = offsetToEntries; + Count = _entries.Length; + } + + #region Methods + + public IEnumerator GetEnumerator() + { + foreach (var entry in _entries) + yield return entry; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Properties + + public uint LowId { get; } + public uint HighId { get; } + public uint OffsetToEntries { get; } + + public int Count { get; } + public MessageTableEntry this[int index] => _entries[index]; + + #endregion + } +} diff --git a/src/Workshell.PE.Resources/Messages/MessageTableEntry.cs b/src/Workshell.PE.Resources/Messages/MessageTableEntry.cs new file mode 100644 index 0000000..79a448a --- /dev/null +++ b/src/Workshell.PE.Resources/Messages/MessageTableEntry.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Workshell.PE.Resources.Messages +{ + public sealed class MessageTableEntry + { + internal MessageTableEntry(uint id, string message, bool isUnicode) + { + Id = id; + Message = message; + IsUnicode = isUnicode; + } + + #region Methods + + public override string ToString() + { + return $"0x{Id:X8}: {Message}"; + } + + #endregion + + #region Properties + + public uint Id { get; } + public string Message { get; } + public bool IsUnicode { get; } + + #endregion + } +} diff --git a/src/Workshell.PE.Resources/Messages/MessageTableResource.cs b/src/Workshell.PE.Resources/Messages/MessageTableResource.cs new file mode 100644 index 0000000..1e805dd --- /dev/null +++ b/src/Workshell.PE.Resources/Messages/MessageTableResource.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Workshell.PE.Extensions; +using Workshell.PE.Resources.Native; + +namespace Workshell.PE.Resources.Messages +{ + public sealed class MessageTableResource : Resource + { + public MessageTableResource(PortableExecutableImage image, ResourceType type, ResourceDirectoryEntry entry, ResourceId id) : base(image, type, entry, id) + { + } + + #region Static Methods + + public static bool Register() + { + var type = new ResourceId(ResourceType.MessageTable); + + return ResourceType.Register(type); + } + + #endregion + + #region Methods + + public MessageTable GetTable() + { + return GetTable(ResourceLanguage.English.UnitedStates); + } + + public async Task GetTableAsync() + { + return await GetTableAsync(ResourceLanguage.English.UnitedStates).ConfigureAwait(false); + } + + public MessageTable GetTable(ResourceLanguage language) + { + return GetTableAsync(language).GetAwaiter().GetResult(); + } + + public async Task GetTableAsync(ResourceLanguage language) + { + var buffer = await GetBytesAsync(language).ConfigureAwait(false); + + using (var mem = new MemoryStream(buffer)) + { + var tuples = new List>(); + var resourceData = await mem.ReadStructAsync().ConfigureAwait(false); + + for(var i = 0; i < resourceData.NumberOfBlocks; i++) + { + var block = await mem.ReadStructAsync().ConfigureAwait(false); + var tuple = new Tuple(block.OffsetToEntries, block.LowId, block.HighId); + + tuples.Add(tuple); + } + + var blocks = new MessageTableBlock[resourceData.NumberOfBlocks]; + + for(var i = 0; i < tuples.Count; i++) + { + var tuple = tuples[i]; + + mem.Seek(tuple.Item1, SeekOrigin.Begin); + + var count = (tuple.Item3 - tuple.Item2).ToInt32() + 1; + var entries = new MessageTableEntry[count]; + var id = tuple.Item2; + + for (var j = 0; j < count; j++) + { + var resourceEntry = await mem.ReadStructAsync().ConfigureAwait(false); + var stringBuffer = await mem.ReadBytesAsync(resourceEntry.Length - sizeof(uint)).ConfigureAwait(false); + var message = (resourceEntry.Flags == 0 ? Encoding.ASCII.GetString(stringBuffer) : Encoding.Unicode.GetString(stringBuffer)); + var entry = new MessageTableEntry(id, message, resourceEntry.Flags != 0); + + entries[j] = entry; + id++; + } + + var block = new MessageTableBlock(tuple.Item2, tuple.Item3, tuple.Item1, entries); + + blocks[i] = block; + } + + var table = new MessageTable(this, language, blocks); + + return table; + } + } + + #endregion + } +} diff --git a/src/Workshell.PE.Resources/Native/MESSAGETABLE.cs b/src/Workshell.PE.Resources/Native/MESSAGETABLE.cs new file mode 100644 index 0000000..a5b8a02 --- /dev/null +++ b/src/Workshell.PE.Resources/Native/MESSAGETABLE.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Workshell.PE.Resources.Native +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MESSAGE_RESOURCE_DATA + { + public uint NumberOfBlocks; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MESSAGE_RESOURCE_BLOCK + { + public uint LowId; + public uint HighId; + public uint OffsetToEntries; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MESSAGE_RESOURCE_ENTRY + { + public ushort Length; + public ushort Flags; + } +} diff --git a/src/Workshell.PE.Resources/Strings/StringTable.cs b/src/Workshell.PE.Resources/Strings/StringTable.cs new file mode 100644 index 0000000..2285a14 --- /dev/null +++ b/src/Workshell.PE.Resources/Strings/StringTable.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Workshell.PE.Resources.Strings +{ + public sealed class StringTable : IEnumerable + { + private readonly StringTableEntry[] _entries; + + internal StringTable(StringTableResource resource, uint languageId, StringTableEntry[] entries) + { + _entries = entries; + + Count = _entries.Length; + Resource = resource; + Language = languageId; + } + + #region Methods + + public IEnumerator GetEnumerator() + { + foreach (var entry in _entries) + yield return entry; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Properties + + public StringTableResource Resource { get; } + public ResourceLanguage Language { get; } + + public int Count { get; } + public StringTableEntry this[int index] => _entries[index]; + + #endregion + } +} diff --git a/src/Workshell.PE.Resources/Strings/StringTableEntry.cs b/src/Workshell.PE.Resources/Strings/StringTableEntry.cs new file mode 100644 index 0000000..6ef470f --- /dev/null +++ b/src/Workshell.PE.Resources/Strings/StringTableEntry.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Workshell.PE.Resources.Strings +{ + public sealed class StringTableEntry + { + internal StringTableEntry(ushort id, string value) + { + Id = id; + Value = value; + } + + #region Methods + + public override string ToString() + { + if (Id == 0) + return "(Empty)"; + + return $"{Id} = {Value}"; + } + + #endregion + + #region Properties + + public ushort Id { get; } + public string Value { get; } + public bool IsEmpty => (Id == 0); + + #endregion + } +} diff --git a/src/Workshell.PE.Resources/Strings/StringTableResource.cs b/src/Workshell.PE.Resources/Strings/StringTableResource.cs new file mode 100644 index 0000000..d8c7841 --- /dev/null +++ b/src/Workshell.PE.Resources/Strings/StringTableResource.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +using Workshell.PE.Extensions; + +namespace Workshell.PE.Resources.Strings +{ + public sealed class StringTableResource : Resource + { + public StringTableResource(PortableExecutableImage image, ResourceType type, ResourceDirectoryEntry entry, ResourceId id) : base(image, type, entry, id) + { + } + + #region Static Methods + + public static bool Register() + { + var typeId = new ResourceId(ResourceType.String); + + return ResourceType.Register(typeId); + } + + #endregion + + #region Methods + + public StringTable GetTable() + { + return GetTable(ResourceLanguage.English.UnitedStates); + } + + public async Task GetTableAsync() + { + return await GetTableAsync(ResourceLanguage.English.UnitedStates).ConfigureAwait(false); + } + + public StringTable GetTable(ResourceLanguage language) + { + return GetTableAsync(language).GetAwaiter().GetResult(); + } + + public async Task GetTableAsync(ResourceLanguage language) + { + var buffer = await GetBytesAsync(language).ConfigureAwait(false); + var strings = new List(); + + using (var mem = new MemoryStream(buffer)) + { + try + { + while (mem.Position < mem.Length) + { + var count = await mem.ReadUInt16Async().ConfigureAwait(false); + + if (count == 0) + { + strings.Add(null); + } + else + { + var builder = new StringBuilder(count); + + for (var i = 0; i < count; i++) + { + var value = await mem.ReadUInt16Async().ConfigureAwait(false); + + builder.Append((char)value); + } + + strings.Add(builder.ToString()); + } + } + } + catch (Exception ex) + { + throw new PortableExecutableImageException(Image, "Could not read string table from stream.", ex); + } + } + + var entries = new StringTableEntry[strings.Count]; + var baseId = Convert.ToUInt16((Id - 1) << 4); + + for (var i = 0; i < strings.Count; i++) + { + var value = strings[i]; + + if (value == null) + { + entries[i] = new StringTableEntry(0, value); + } + else + { + var id = Convert.ToUInt16(baseId + i); + + entries[i] = new StringTableEntry(id, value); + } + } + + var table = new StringTable(this, language, entries); + + return table; + } + + #endregion + } +} diff --git a/src/Workshell.PE.Testbed/Program.cs b/src/Workshell.PE.Testbed/Program.cs index e13aa25..08a2cd8 100644 --- a/src/Workshell.PE.Testbed/Program.cs +++ b/src/Workshell.PE.Testbed/Program.cs @@ -9,6 +9,8 @@ using Workshell.PE.Resources.Dialogs; using Workshell.PE.Resources.Dialogs.Styles; using Workshell.PE.Resources.Menus; +using Workshell.PE.Resources.Messages; +using Workshell.PE.Resources.Strings; namespace Workshell.PE.Testbed { @@ -25,16 +27,18 @@ static async Task RunAsync(string[] args) DialogResource.Register(); MenuResource.Register(); + StringTableResource.Register(); + MessageTableResource.Register(); //var image = await PortableExecutableImage.FromFileAsync(@"C:\Users\lkinsella\Downloads\IISCrypto.exe"); //var image = await PortableExecutableImage.FromFileAsync(@"C:\Windows\System32\shell32.dll"); - var image = await PortableExecutableImage.FromFileAsync(@"C:\Windows\System32\en-GB\notepad.exe.mui"); + var image = await PortableExecutableImage.FromFileAsync(@"C:\Windows\System32\en-GB\user32.dll.mui"); var dataDirectory = image.NTHeaders.DataDirectories[DataDirectoryType.ResourceTable]; var content = await dataDirectory.GetContentAsync().ConfigureAwait(false); var resources = await ResourceCollection.GetAsync(image); - var menus = resources.FirstOrDefault(res => res.Id == ResourceType.Menu); - var menuResource = (MenuResource) menus.FirstOrDefault(); - var menu = await menuResource.GetMenuAsync(ResourceLanguage.English.UnitedKingdom); + var messageResources = resources.FirstOrDefault(res => res.Id == ResourceType.MessageTable); + var messageResource = (MessageTableResource)messageResources.FirstOrDefault(); + var table = messageResource.GetTable(ResourceLanguage.English.UnitedKingdom); } } }