public sealed class ExportDirectoryParser
{
public IReadOnlyList<ExportSymbolInfo> Parse(byte[] rawBytes, NtHeaders nt, IReadOnlyList<SectionInfo> sections, SafetyLimits limits)
{
if (nt.OptionalHeader.DataDirectories.Count == 0)
{
return Array.Empty<ExportSymbolInfo>();
}
var dir = nt.OptionalHeader.DataDirectories[0];
if (dir.VirtualAddress == 0 || dir.Size == 0)
{
return Array.Empty<ExportSymbolInfo>();
}
var offset = RvaMapper.RvaToFileOffset(dir.VirtualAddress, sections, nt.OptionalHeader.SizeOfHeaders);
if (offset is null)
{
return Array.Empty<ExportSymbolInfo>();
}
BoundsChecker.EnsureRange(offset.Value, 40, rawBytes.Length, "export directory");
using var ms = new MemoryStream(rawBytes, writable: false);
using var reader = new BinaryReader(ms);
ms.Position = offset.Value;
_ = reader.ReadUInt32();
_ = reader.ReadUInt32();
_ = reader.ReadUInt16();
_ = reader.ReadUInt16();
_ = reader.ReadUInt32();
var baseOrdinal = reader.ReadUInt32();
var numberOfFunctions = reader.ReadUInt32();
var numberOfNames = reader.ReadUInt32();
var addressOfFunctions = reader.ReadUInt32();
var addressOfNames = reader.ReadUInt32();
var addressOfNameOrdinals = reader.ReadUInt32();
BoundsChecker.EnsureCount((int)numberOfNames, limits.MaxExportsCount, "Export names");
var functionsOffset = RvaMapper.RvaToFileOffset(addressOfFunctions, sections, nt.OptionalHeader.SizeOfHeaders);
var namesOffset = RvaMapper.RvaToFileOffset(addressOfNames, sections, nt.OptionalHeader.SizeOfHeaders);
var ordinalsOffset = RvaMapper.RvaToFileOffset(addressOfNameOrdinals, sections, nt.OptionalHeader.SizeOfHeaders);
if (functionsOffset is null || namesOffset is null || ordinalsOffset is null)
{
return Array.Empty<ExportSymbolInfo>();
}
BoundsChecker.EnsureRange(functionsOffset.Value, checked((long)numberOfFunctions * 4), rawBytes.Length, "export address table");
BoundsChecker.EnsureRange(namesOffset.Value, checked((long)numberOfNames * 4), rawBytes.Length, "export name pointer table");
BoundsChecker.EnsureRange(ordinalsOffset.Value, checked((long)numberOfNames * 2), rawBytes.Length, "export ordinal table");
var exports = new List<ExportSymbolInfo>();
for (var i = 0; i < numberOfNames; i++)
{
BoundsChecker.EnsureCount(exports.Count + 1, limits.MaxExportsCount, "Export symbols");
var namePtr = BitConverter.ToUInt32(rawBytes, namesOffset.Value + i * 4);
var ordinalIndex = BitConverter.ToUInt16(rawBytes, ordinalsOffset.Value + i * 2);
if (ordinalIndex >= numberOfFunctions)
{
continue;
}
var functionRva = BitConverter.ToUInt32(rawBytes, functionsOffset.Value + ordinalIndex * 4);
var nameOffset = RvaMapper.RvaToFileOffset(namePtr, sections, nt.OptionalHeader.SizeOfHeaders);
if (nameOffset is null)
{
continue;
}
var symbolName = BinaryReaderExtensions.ReadAsciiStringAt(rawBytes, nameOffset.Value, 1024);
var isForwarder = functionRva >= dir.VirtualAddress && functionRva < dir.VirtualAddress + dir.Size;
string? forwarder = null;
if (isForwarder)
{
var forwarderOffset = RvaMapper.RvaToFileOffset(functionRva, sections, nt.OptionalHeader.SizeOfHeaders);
if (forwarderOffset is not null)
{
forwarder = BinaryReaderExtensions.ReadAsciiStringAt(rawBytes, forwarderOffset.Value, 1024);
}
}
exports.Add(new ExportSymbolInfo(
symbolName,
(ushort)(baseOrdinal + ordinalIndex),
functionRva,
isForwarder,
forwarder));
}
return exports;
}
}