From e18d4c26c368f414f1cdc3c2930c9ff87711c2f1 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 9 Mar 2018 15:29:57 +0000 Subject: [PATCH] Decode Windows resources from PE. --- libexeinfo/PE/Info.cs | 37 +++++++++++ libexeinfo/PE/PE.cs | 139 ++++++++++++++++++++++++++++++++++----- libexeinfo/PE/Structs.cs | 10 +++ 3 files changed, 169 insertions(+), 17 deletions(-) diff --git a/libexeinfo/PE/Info.cs b/libexeinfo/PE/Info.cs index ae75f3c..6236a58 100644 --- a/libexeinfo/PE/Info.cs +++ b/libexeinfo/PE/Info.cs @@ -195,8 +195,45 @@ namespace libexeinfo sb.AppendFormat("\tExecutable contains debug information of type {0}", debugDirectory.type) .AppendLine(); + if(WindowsResourcesRoot != null) + { + sb.AppendLine("\tResources:"); + PrintResourceNode(sb, WindowsResourcesRoot, 2); + } + return sb.ToString(); } } + + static void PrintResourceNode(StringBuilder sb, ResourceNode node, int level) + { + for(int i = 0; i < level; i++) sb.Append("\t"); + if(node.children != null) + { + switch(node.level) + { + case 0: + sb.AppendFormat("Root contains {0} types:", node.children.Length).AppendLine(); + break; + case 1: + sb.AppendFormat("Type {0} has {1} items:", node.name, node.children.Length).AppendLine(); + break; + case 2: + sb.AppendFormat("ID {0} has {1} languages:", node.id, node.children.Length).AppendLine(); + break; + default: + sb.AppendFormat("ID {0} has {1} items:", node.id, node.children.Length).AppendLine(); + break; + } + + foreach(ResourceNode child in node.children) PrintResourceNode(sb, child, level + 1); + } + + if(node.data == null) return; + + if(node.level == 3) + sb.AppendFormat("{0} contains {1} bytes.", node.name, node.data.Length).AppendLine(); + else sb.AppendFormat("ID {0} contains {1} bytes.", node.id, node.data.Length).AppendLine(); + } } } \ No newline at end of file diff --git a/libexeinfo/PE/PE.cs b/libexeinfo/PE/PE.cs index 51587c7..afccef5 100644 --- a/libexeinfo/PE/PE.cs +++ b/libexeinfo/PE/PE.cs @@ -26,18 +26,18 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using libexeinfo.Windows; namespace libexeinfo { /// /// Represents a Microsoft Portable Executable /// - // TODO: Process BeOS resources - // TODO: Process Windows resources public partial class PE : IExecutable { MZ baseExecutable; @@ -51,6 +51,7 @@ namespace libexeinfo string[] importedNames; string moduleName; COFF.SectionHeader[] sectionHeaders; + public ResourceNode WindowsResourcesRoot; WindowsHeader64 winHeader; /// @@ -367,23 +368,28 @@ namespace libexeinfo debugDirectory = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); } - // BeOS .rsrc is not virtual addressing, and has no size, solve it - if(reqOs.Name == "BeOS" && newSectionHeaders.ContainsKey(".rsrc")) - { - newSectionHeaders.TryGetValue(".rsrc", out COFF.SectionHeader beRsrc); - newSectionHeaders.Remove(".rsrc"); - beRsrc.pointerToRawData = beRsrc.virtualAddress; + if(newSectionHeaders.TryGetValue(".rsrc", out COFF.SectionHeader rsrc)) + if(reqOs.Name == "BeOS") + { + newSectionHeaders.Remove(".rsrc"); + rsrc.pointerToRawData = rsrc.virtualAddress; - long maxPosition = BaseStream.Length; - foreach(KeyValuePair kvp in newSectionHeaders) - if(kvp.Value.pointerToRawData <= maxPosition && - kvp.Value.pointerToRawData > beRsrc.pointerToRawData) - maxPosition = kvp.Value.pointerToRawData; + long maxPosition = BaseStream.Length; + foreach(KeyValuePair kvp in newSectionHeaders) + if(kvp.Value.pointerToRawData <= maxPosition && + kvp.Value.pointerToRawData > rsrc.pointerToRawData) + maxPosition = kvp.Value.pointerToRawData; - beRsrc.sizeOfRawData = (uint)(maxPosition - beRsrc.pointerToRawData); - beRsrc.virtualSize = beRsrc.sizeOfRawData; - newSectionHeaders.Add(".rsrc", beRsrc); - } + rsrc.sizeOfRawData = (uint)(maxPosition - rsrc.pointerToRawData); + rsrc.virtualSize = rsrc.sizeOfRawData; + newSectionHeaders.Add(".rsrc", rsrc); + + // TODO: Decode BeOS resource format + } + else + WindowsResourcesRoot = GetResourceNode(BaseStream, rsrc.pointerToRawData, + rsrc.virtualAddress, + rsrc.pointerToRawData, 0, null, 0); sectionHeaders = newSectionHeaders.Values.OrderBy(s => s.pointerToRawData).ToArray(); Segment[] segments = new Segment[sectionHeaders.Length]; @@ -401,6 +407,105 @@ namespace libexeinfo Strings = strings; } + static ResourceNode GetResourceNode(Stream stream, long position, long rsrcVa, long rsrcStart, uint id, + string name, int level) + { + long oldPosition = stream.Position; + ResourceNode thisNode = new ResourceNode {name = name, id = id, level = level}; + + if(thisNode.name == null) + thisNode.name = level == 1 ? Resources.IdToName((ushort)thisNode.id) : $"{thisNode.id}"; + + stream.Position = position; + byte[] buffer = new byte[Marshal.SizeOf(typeof(ResourceDirectoryTable))]; + stream.Read(buffer, 0, buffer.Length); + ResourceDirectoryTable rsrcTable = + BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + + buffer = new byte[Marshal.SizeOf(typeof(ResourceDirectoryEntries))]; + ResourceDirectoryEntries[] entries = + new ResourceDirectoryEntries[rsrcTable.nameEntries + rsrcTable.idEntries]; + + for(int i = 0; i < rsrcTable.nameEntries; i++) + { + stream.Read(buffer, 0, buffer.Length); + entries[i] = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + } + + for(int i = 0; i < rsrcTable.idEntries; i++) + { + stream.Read(buffer, 0, buffer.Length); + entries[rsrcTable.nameEntries + i] = + BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + } + + thisNode.children = new ResourceNode[entries.Length]; + + for(int i = 0; i < rsrcTable.nameEntries; i++) + { + byte[] len = new byte[2]; + + stream.Position = rsrcStart + (entries[i].nameOrID & 0x7FFFFFFF); + stream.Read(len, 0, 2); + buffer = new byte[BitConverter.ToUInt16(len, 0) * 2]; + stream.Read(buffer, 0, buffer.Length); + string childName = Encoding.Unicode.GetString(buffer); + + if((entries[i].rva & 0x80000000) == 0x80000000) + thisNode.children[i] = GetResourceNode(stream, rsrcStart + (entries[i].rva & 0x7FFFFFFF), rsrcVa, + rsrcStart, 0, + childName, level + 1); + else + { + buffer = new byte[Marshal.SizeOf(typeof(ResourceDataEntry))]; + stream.Position = rsrcStart + (entries[i].rva & 0x7FFFFFFF); + stream.Read(buffer, 0, buffer.Length); + ResourceDataEntry dataEntry = + BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + thisNode.children[i] = new ResourceNode + { + data = new byte[dataEntry.size], + id = 0, + name = childName, + level = level + 1 + }; + stream.Position = dataEntry.rva - (rsrcVa - rsrcStart); + stream.Read(thisNode.children[i].data, 0, (int)dataEntry.size); + } + } + + for(int i = rsrcTable.nameEntries; i < rsrcTable.nameEntries + rsrcTable.idEntries; i++) + if((entries[i].rva & 0x80000000) == 0x80000000) + thisNode.children[i] = GetResourceNode(stream, rsrcStart + (entries[i].rva & 0x7FFFFFFF), rsrcVa, + rsrcStart, entries[i].nameOrID & 0x7FFFFFFF, null, + level + 1); + else + { + buffer = new byte[Marshal.SizeOf(typeof(ResourceDataEntry))]; + stream.Position = rsrcStart + (entries[i].rva & 0x7FFFFFFF); + stream.Read(buffer, 0, buffer.Length); + ResourceDataEntry dataEntry = + BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + thisNode.children[i] = new ResourceNode + { + data = new byte[dataEntry.size], + id = entries[i].nameOrID & 0x7FFFFFFF, + name = $"{entries[i].nameOrID & 0x7FFFFFFF}", + level = level + 1 + }; + + if(level == 2) + try { thisNode.children[i].name = new CultureInfo((int)thisNode.children[i].id).DisplayName; } + catch { thisNode.children[i].name = $"Language ID {thisNode.children[i].id}"; } + + stream.Position = dataEntry.rva - (rsrcVa - rsrcStart); + stream.Read(thisNode.children[i].data, 0, (int)dataEntry.size); + } + + stream.Position = oldPosition; + return thisNode; + } + /// /// Identifies if the specified executable is a Microsoft Portable Executable /// diff --git a/libexeinfo/PE/Structs.cs b/libexeinfo/PE/Structs.cs index 4fdaf6d..3c12754 100644 --- a/libexeinfo/PE/Structs.cs +++ b/libexeinfo/PE/Structs.cs @@ -24,6 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using System; using System.Runtime.InteropServices; using static libexeinfo.COFF; @@ -449,5 +450,14 @@ namespace libexeinfo /// public uint importAddressTableRva; } + + public class ResourceNode + { + public uint id; + public string name; + public ResourceNode[] children; + public byte[] data; + public int level; + } } } \ No newline at end of file