Win32 Resources¶
Note
The documentation has a new home: Check it out!
Win32 resources are additional files embedded into the PE image, and are typically stored in the .rsrc
section. All classes relevant to Win32 resources can be found in the following namespace:
using AsmResolver.PE.Win32Resources;
Directories¶
Resources are exposed by the IPEImage.Resources
property, which represents the root directory of all resources stored in the image. Every directory (including the root directory) is represented by instances of IResourceDirectory
. This type contains the Entries
property. Entries in a directory can either be another sub directory containing more entries, or a data entry (an instance of IResourceData
) with the raw contents of the resource.
IPEImage image = ...
IResourceDirectory root = image.Resources;
foreach (var entry in root.Entries)
{
if (entry.IsDirectory)
Console.WriteLine("Directory {0}.", entry.Id);
else // if (entry.IsData)
Console.WriteLine("Data {0}.", entry.Id);
}
Alternatively, you can access specific resources very easily by using the GetDirectory
and GetData
:
IPEImage image = ...
IResourceData stringDataEntry = image.Resources
.GetDirectory(ResourceType.String) // Get string tables directory.
.GetDirectory(251) // Get string block with ID 251
.GetData(1033); // Get string with language ID 1033
Adding or replacing entries can be by either modifying the Entries
property directly, or by using the AddOrReplace
method. The latter is recommended as it ensures that an existing entry with the same ID is replaced with the new one.
IPEImage image = ...
var newDirectory = new ResourceDirectory(ResourceType.String);
image.Resources.Entries.Add(newDirectory);
IPEImage image = ...
var newDirectory = new ResourceDirectory(ResourceType.String);
image.Resources.AddOrReplaceEntry(newDirectory);
Similarly, removing can be done by modifying the Entries
directory, or by using the RemoveEntry
method:
IPEImage image = ...
image.Resources.RemoveEntry(ResourceType.String);
Data Entries¶
Data entries are represented using the IResourceData
interface, and contain a property called Contents
which is of type ISegment
. You can check if this is a IReadableSegment
, or use the shortcuts CanRead
and CreateReader
methods instead to read the raw contents of the entry.
IPEImage image = ...
IResourceData dataEntry = image.Resources
.GetDirectory(ResourceType.String) // Get string tables directory.
.GetDirectory(251) // Get string block with ID 251
.GetData(1033); // Get string with language ID 1033
if (dataEntry.CanRead)
{
BinaryStreamReader reader = dataEntry.CreateReader();
// ...
}
Adding new data entries can be done by using the ResourceData
constructor:
IPEImage image = ...
var data = new ResourceData(id: 1033, contents: new DataSegment(new byte[] { ... }));
image.Resources
.GetDirectory(ResourceType.String)
.GetDirectory(251)
.AddOrReplaceEntry(data);
Example Traversal¶
The following example is a program that dumps the resources tree from a single PE image.
private const int IndentationWidth = 3;
private static void Main(string[] args)
{
// Open the PE image.
string filePath = args[0].Replace("\"", "");
var peImage = PEImage.FromFile(filePath);
// Dump the resources.
PrintResourceDirectory(peImage.Resources);
}
private static void PrintResourceEntry(IResourceEntry entry, int indentationLevel = 0)
{
// Decide if we are dealing with a sub directory or a data entry.
if (entry.IsDirectory)
PrintResourceDirectory((IResourceDirectory) entry, indentationLevel);
else if (entry.IsData)
PrintResourcData((IResourceData) entry, indentationLevel);
}
private static void PrintResourceDirectory(IResourceDirectory directory, int indentationLevel = 0)
{
string indentation = new string(' ', indentationLevel * IndentationWidth);
// Print the name or ID of the directory.
string displayName = directory.Name ?? "ID: " + directory.Id;
Console.WriteLine("{0}+- Directory {1}", indentation, displayName);
// Print all entries in the directory.
foreach (var entry in directory.Entries)
PrintResourceEntry(entry, indentationLevel + 1);
}
private static void PrintResourcData(IResourceData data, int indentationLevel)
{
string indentation = new string(' ', indentationLevel * IndentationWidth);
// Print the name of the data entry, as well as the size of the contents.
string displayName = data.Name ?? "ID: " + data.Id;
Console.WriteLine("{0}+- Data {1} ({2} bytes)", indentation, displayName, data.Contents.GetPhysicalSize());
}