public sealed class ResourceDirectoryParser
{
public ResourceInfo? Parse(byte[] rawBytes, NtHeaders nt, IReadOnlyList<SectionInfo> sections, SafetyLimits limits)
{
if (nt.OptionalHeader.DataDirectories.Count <= 2)
{
return null;
}
var dir = nt.OptionalHeader.DataDirectories[2];
if (dir.VirtualAddress == 0 || dir.Size == 0)
{
return null;
}
var baseOffset = RvaMapper.RvaToFileOffset(dir.VirtualAddress, sections, nt.OptionalHeader.SizeOfHeaders);
if (baseOffset is null)
{
return null;
}
BoundsChecker.EnsureRange(baseOffset.Value, 16, rawBytes.Length, "resource directory root");
var root = ParseDirectory(rawBytes, nt, sections, limits, dir.VirtualAddress, baseOffset.Value, 0, null, null);
return new ResourceInfo(root);
}
private ResourceNode ParseDirectory(
byte[] rawBytes,
NtHeaders nt,
IReadOnlyList<SectionInfo> sections,
SafetyLimits limits,
uint resourceBaseRva,
int resourceBaseOffset,
int relativeOffset,
string? name,
ushort? id,
int depth = 0)
{
if (depth > limits.MaxResourceTreeRecursionDepth)
throw new SafetyException("Resource tree recursion depth exceeded.");
var offset = checked(resourceBaseOffset + relativeOffset);
BoundsChecker.EnsureRange(offset, 16, rawBytes.Length, "resource directory header");
var numberOfNamedEntries = BitConverter.ToUInt16(rawBytes, offset + 12);
var numberOfIdEntries = BitConverter.ToUInt16(rawBytes, offset + 14);
var total = numberOfNamedEntries + numberOfIdEntries;
BoundsChecker.EnsureCount(total, limits.MaxImportsCount, "Resource entries");
var children = new List<ResourceNode>();
var entryOffset = checked(offset + 16);
for (var i = 0; i < total; i++)
{
var currentEntryOffset = checked(entryOffset + i * 8);
BoundsChecker.EnsureRange(currentEntryOffset, 8, rawBytes.Length, "resource directory entry");
var nameOrId = BitConverter.ToUInt32(rawBytes, currentEntryOffset);
var dataOrSubdir = BitConverter.ToUInt32(rawBytes, currentEntryOffset + 4);
string? childName = null;
ushort? childId = null;
if ((nameOrId & 0x80000000) != 0)
{
var strRel = (int)(nameOrId & 0x7FFFFFFF);
var strOffset = checked(resourceBaseOffset + strRel);
BoundsChecker.EnsureRange(strOffset, 2, rawBytes.Length, "resource unicode length");
var len = BitConverter.ToUInt16(rawBytes, strOffset);
BoundsChecker.EnsureRange(strOffset + 2, checked((long)len * 2), rawBytes.Length, "resource unicode string");
childName = BinaryReaderExtensions.ReadUnicodeStringAt(rawBytes, strOffset + 2, len);
}
else
{
childId = (ushort)(nameOrId & 0xFFFF);
}
if ((dataOrSubdir & 0x80000000) != 0)
{
var subdirRel = (int)(dataOrSubdir & 0x7FFFFFFF);
children.Add(ParseDirectory(rawBytes, nt, sections, limits, resourceBaseRva, resourceBaseOffset, subdirRel, childName, childId, depth + 1));
}
else
{
var dataEntryOffset = checked(resourceBaseOffset + (int)dataOrSubdir);
BoundsChecker.EnsureRange(dataEntryOffset, 16, rawBytes.Length, "resource data entry");
var dataRva = BitConverter.ToUInt32(rawBytes, dataEntryOffset);
var size = BitConverter.ToUInt32(rawBytes, dataEntryOffset + 4);
var codePage = BitConverter.ToUInt32(rawBytes, dataEntryOffset + 8);
var reserved = BitConverter.ToUInt32(rawBytes, dataEntryOffset + 12);
children.Add(new ResourceNode(
childName,
childId,
false,
Array.Empty<ResourceNode>(),
new ResourceDataInfo(dataRva, size, codePage, reserved)));
}
}
return new ResourceNode(name, id, true, children, null);
}
}