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