obfuz/Plugins/dnlib/DotNet/Writer/NativeModuleWriter.cs

845 lines
31 KiB
C#

// dnlib: See LICENSE.txt for more info
using System;
using System.Collections.Generic;
using dnlib.IO;
using dnlib.PE;
using dnlib.W32Resources;
using dnlib.DotNet.MD;
using System.Diagnostics;
namespace dnlib.DotNet.Writer {
/// <summary>
/// <see cref="NativeModuleWriter"/> options
/// </summary>
public sealed class NativeModuleWriterOptions : ModuleWriterOptionsBase {
/// <summary>
/// If <c>true</c>, any extra data after the PE data in the original file is also saved
/// at the end of the new file. Enable this option if some protector has written data to
/// the end of the file and uses it at runtime.
/// </summary>
public bool KeepExtraPEData { get; set; }
/// <summary>
/// If <c>true</c>, keep the original Win32 resources
/// </summary>
public bool KeepWin32Resources { get; set; }
internal bool OptimizeImageSize { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="module">Module</param>
/// <param name="optimizeImageSize">true to optimize the image size so it's as small as possible.
/// Since the file can contain native methods and other native data, we re-use the
/// original file when writing the new file. If <paramref name="optimizeImageSize"/> is true,
/// we'll try to re-use the old method body locations in the original file and
/// also try to fit the new metadata in the old metadata location.</param>
public NativeModuleWriterOptions(ModuleDefMD module, bool optimizeImageSize) : base(module) {
// C++ .NET mixed mode assemblies sometimes/often call Module.ResolveMethod(),
// so method metadata tokens must be preserved.
MetadataOptions.Flags |= MetadataFlags.PreserveAllMethodRids;
if (optimizeImageSize) {
OptimizeImageSize = true;
// Prevent the #Blob heap from getting bigger. Encoded TypeDefOrRef tokens are stored there (in
// typesigs and callingconvsigs) so we must preserve TypeDefOrRef tokens (or the #Blob heap could
// grow in size and new MD won't fit in old location)
MetadataOptions.Flags |= MetadataFlags.PreserveTypeRefRids | MetadataFlags.PreserveTypeDefRids | MetadataFlags.PreserveTypeSpecRids;
}
}
}
/// <summary>
/// A module writer that supports saving mixed-mode modules (modules with native code).
/// The original image will be re-used. See also <see cref="ModuleWriter"/>
/// </summary>
public sealed class NativeModuleWriter : ModuleWriterBase {
/// <summary>The original .NET module</summary>
readonly ModuleDefMD module;
/// <summary>All options</summary>
NativeModuleWriterOptions options;
/// <summary>
/// Any extra data found at the end of the original file. This is <c>null</c> if there's
/// no extra data or if <see cref="NativeModuleWriterOptions.KeepExtraPEData"/> is
/// <c>false</c>.
/// </summary>
DataReaderChunk extraData;
/// <summary>The original PE sections and their data</summary>
List<OrigSection> origSections;
readonly struct ReusedChunkInfo {
public IReuseChunk Chunk { get; }
public RVA RVA { get; }
public ReusedChunkInfo(IReuseChunk chunk, RVA rva) {
Chunk = chunk;
RVA = rva;
}
}
List<ReusedChunkInfo> reusedChunks;
/// <summary>Original PE image</summary>
readonly IPEImage peImage;
/// <summary>New sections we've added and their data</summary>
List<PESection> sections;
/// <summary>New .text section where we put some stuff, eg. .NET metadata</summary>
PESection textSection;
/// <summary>The new COR20 header</summary>
ByteArrayChunk imageCor20Header;
/// <summary>
/// New .rsrc section where we put the new Win32 resources. This is <c>null</c> if there
/// are no Win32 resources or if <see cref="NativeModuleWriterOptions.KeepWin32Resources"/>
/// is <c>true</c>
/// </summary>
PESection rsrcSection;
/// <summary>
/// Offset in <see cref="ModuleWriterBase.destStream"/> of the PE checksum field.
/// </summary>
long checkSumOffset;
/// <summary>
/// Original PE section
/// </summary>
public sealed class OrigSection : IDisposable {
/// <summary>PE section</summary>
public ImageSectionHeader PESection;
/// <summary>PE section data</summary>
public DataReaderChunk Chunk;
/// <summary>
/// Constructor
/// </summary>
/// <param name="peSection">PE section</param>
public OrigSection(ImageSectionHeader peSection) =>
PESection = peSection;
/// <inheritdoc/>
public void Dispose() {
Chunk = null;
PESection = null;
}
/// <inheritdoc/>
public override string ToString() {
uint offs = Chunk.CreateReader().StartOffset;
return $"{PESection.DisplayName} FO:{offs:X8} L:{Chunk.CreateReader().Length:X8}";
}
}
/// <summary>
/// Gets the module
/// </summary>
public ModuleDefMD ModuleDefMD => module;
/// <inheritdoc/>
public override ModuleDef Module => module;
/// <inheritdoc/>
public override ModuleWriterOptionsBase TheOptions => Options;
/// <summary>
/// Gets/sets the writer options. This is never <c>null</c>
/// </summary>
public NativeModuleWriterOptions Options {
get => options ??= new NativeModuleWriterOptions(module, optimizeImageSize: true);
set => options = value;
}
/// <summary>
/// Gets all <see cref="PESection"/>s
/// </summary>
public override List<PESection> Sections => sections;
/// <summary>
/// Gets the original PE sections and their data
/// </summary>
public List<OrigSection> OrigSections => origSections;
/// <summary>
/// Gets the <c>.text</c> section
/// </summary>
public override PESection TextSection => textSection;
/// <summary>
/// Gets the <c>.rsrc</c> section or <c>null</c> if there's none
/// </summary>
public override PESection RsrcSection => rsrcSection;
/// <summary>
/// Constructor
/// </summary>
/// <param name="module">The module</param>
/// <param name="options">Options or <c>null</c></param>
public NativeModuleWriter(ModuleDefMD module, NativeModuleWriterOptions options) {
this.module = module;
this.options = options;
peImage = module.Metadata.PEImage;
reusedChunks = new List<ReusedChunkInfo>();
}
/// <inheritdoc/>
protected override long WriteImpl() {
try {
return Write();
}
finally {
if (origSections is not null) {
foreach (var section in origSections)
section.Dispose();
}
}
}
long Write() {
Initialize();
// It's not safe to create new Field RVAs so re-use them all. The user can override
// this by setting field.RVA = 0 when creating a new field.InitialValue.
metadata.KeepFieldRVA = true;
metadata.CreateTables();
return WriteFile();
}
void Initialize() {
CreateSections();
OnWriterEvent(ModuleWriterEvent.PESectionsCreated);
CreateChunks();
OnWriterEvent(ModuleWriterEvent.ChunksCreated);
AddChunksToSections();
OnWriterEvent(ModuleWriterEvent.ChunksAddedToSections);
}
void CreateSections() {
CreatePESections();
CreateRawSections();
CreateExtraData();
}
void CreateChunks() {
CreateMetadataChunks(module);
methodBodies.CanReuseOldBodyLocation = Options.OptimizeImageSize;
CreateDebugDirectory();
imageCor20Header = new ByteArrayChunk(new byte[0x48]);
CreateStrongNameSignature();
}
void AddChunksToSections() {
textSection.Add(imageCor20Header, DEFAULT_COR20HEADER_ALIGNMENT);
textSection.Add(strongNameSignature, DEFAULT_STRONGNAMESIG_ALIGNMENT);
textSection.Add(constants, DEFAULT_CONSTANTS_ALIGNMENT);
textSection.Add(methodBodies, DEFAULT_METHODBODIES_ALIGNMENT);
textSection.Add(netResources, DEFAULT_NETRESOURCES_ALIGNMENT);
textSection.Add(metadata, DEFAULT_METADATA_ALIGNMENT);
textSection.Add(debugDirectory, DebugDirectory.DEFAULT_DEBUGDIRECTORY_ALIGNMENT);
if (rsrcSection is not null)
rsrcSection.Add(win32Resources, DEFAULT_WIN32_RESOURCES_ALIGNMENT);
}
/// <inheritdoc/>
protected override Win32Resources GetWin32Resources() {
if (Options.KeepWin32Resources)
return null;
if (Options.NoWin32Resources)
return null;
return Options.Win32Resources ?? module.Win32Resources;
}
void CreatePESections() {
sections = new List<PESection>();
sections.Add(textSection = new PESection(".text", 0x60000020));
if (GetWin32Resources() is not null)
sections.Add(rsrcSection = new PESection(".rsrc", 0x40000040));
}
/// <summary>
/// Gets the raw section data of the image. The sections are saved in
/// <see cref="origSections"/>.
/// </summary>
void CreateRawSections() {
var fileAlignment = peImage.ImageNTHeaders.OptionalHeader.FileAlignment;
origSections = new List<OrigSection>(peImage.ImageSectionHeaders.Count);
foreach (var peSection in peImage.ImageSectionHeaders) {
var newSection = new OrigSection(peSection);
origSections.Add(newSection);
uint sectionSize = Utils.AlignUp(peSection.SizeOfRawData, fileAlignment);
newSection.Chunk = new DataReaderChunk(peImage.CreateReader(peSection.VirtualAddress, sectionSize), peSection.VirtualSize);
}
}
/// <summary>
/// Creates the PE header "section"
/// </summary>
DataReaderChunk CreateHeaderSection(out IChunk extraHeaderData) {
uint afterLastSectHeader = GetOffsetAfterLastSectionHeader() + (uint)sections.Count * 0x28;
uint firstRawOffset = Math.Min(GetFirstRawDataFileOffset(), peImage.ImageNTHeaders.OptionalHeader.SectionAlignment);
uint headerLen = afterLastSectHeader;
if (firstRawOffset > headerLen)
headerLen = firstRawOffset;
headerLen = Utils.AlignUp(headerLen, peImage.ImageNTHeaders.OptionalHeader.FileAlignment);
if (headerLen <= peImage.ImageNTHeaders.OptionalHeader.SectionAlignment) {
uint origSizeOfHeaders = peImage.ImageNTHeaders.OptionalHeader.SizeOfHeaders;
uint extraLen;
if (headerLen <= origSizeOfHeaders)
extraLen = 0;
else {
extraLen = headerLen - origSizeOfHeaders;
headerLen = origSizeOfHeaders;
}
if (extraLen > 0)
extraHeaderData = new ByteArrayChunk(new byte[(int)extraLen]);
else
extraHeaderData = null;
return new DataReaderChunk(peImage.CreateReader((FileOffset)0, headerLen));
}
//TODO: Support this too
throw new ModuleWriterException("Could not create header");
}
uint GetOffsetAfterLastSectionHeader() {
var lastSect = peImage.ImageSectionHeaders[peImage.ImageSectionHeaders.Count - 1];
return (uint)lastSect.EndOffset;
}
uint GetFirstRawDataFileOffset() {
uint len = uint.MaxValue;
foreach (var section in peImage.ImageSectionHeaders)
len = Math.Min(len, section.PointerToRawData);
return len;
}
/// <summary>
/// Saves any data that is appended to the original PE file
/// </summary>
void CreateExtraData() {
if (!Options.KeepExtraPEData)
return;
var lastOffs = GetLastFileSectionOffset();
extraData = new DataReaderChunk(peImage.CreateReader((FileOffset)lastOffs));
if (extraData.CreateReader().Length == 0)
extraData = null;
}
uint GetLastFileSectionOffset() {
uint rva = 0;
foreach (var sect in origSections)
rva = Math.Max(rva, (uint)sect.PESection.VirtualAddress + sect.PESection.SizeOfRawData);
return (uint)peImage.ToFileOffset((RVA)(rva - 1)) + 1;
}
void ReuseIfPossible(PESection section, IReuseChunk chunk, RVA origRva, uint origSize, uint requiredAlignment) {
if (origRva == 0 || origSize == 0)
return;
if (chunk is null)
return;
if (!chunk.CanReuse(origRva, origSize))
return;
if (((uint)origRva & (requiredAlignment - 1)) != 0)
return;
if (section.Remove(chunk) is null)
throw new InvalidOperationException();
reusedChunks.Add(new ReusedChunkInfo(chunk, origRva));
}
FileOffset GetNewFileOffset(RVA rva) {
foreach (var sect in origSections) {
var section = sect.PESection;
if (section.VirtualAddress <= rva && rva < section.VirtualAddress + Math.Max(section.VirtualSize, section.SizeOfRawData))
return sect.Chunk.FileOffset + (rva - section.VirtualAddress);
}
return (FileOffset)rva;
}
long WriteFile() {
bool entryPointIsManagedOrNoEntryPoint = GetEntryPoint(out uint entryPointToken);
OnWriterEvent(ModuleWriterEvent.BeginWritePdb);
WritePdbFile();
OnWriterEvent(ModuleWriterEvent.EndWritePdb);
metadata.OnBeforeSetOffset();
OnWriterEvent(ModuleWriterEvent.BeginCalculateRvasAndFileOffsets);
if (Options.OptimizeImageSize) {
// Check if we can reuse the old MD location for the new MD.
// If we can't reuse it, it could be due to several reasons:
// - TypeDefOrRef tokens weren't preserved resulting in a new #Blob heap that's bigger than the old #Blob heap
// - New MD was added or existing MD was modified (eg. types were renamed) by the user so it's
// now bigger and doesn't fit in the old location
// - The original location wasn't aligned properly
// - The new MD is bigger because the other MD writer was slightly better at optimizing the MD.
// This should be considered a bug.
var mdDataDir = module.Metadata.ImageCor20Header.Metadata;
metadata.SetOffset(peImage.ToFileOffset(mdDataDir.VirtualAddress), mdDataDir.VirtualAddress);
ReuseIfPossible(textSection, metadata, mdDataDir.VirtualAddress, mdDataDir.Size, DEFAULT_METADATA_ALIGNMENT);
var resourceDataDir = peImage.ImageNTHeaders.OptionalHeader.DataDirectories[2];
if (win32Resources is not null && resourceDataDir.VirtualAddress != 0 && resourceDataDir.Size != 0) {
var win32ResourcesOffset = peImage.ToFileOffset(resourceDataDir.VirtualAddress);
if (win32Resources.CheckValidOffset(win32ResourcesOffset)) {
win32Resources.SetOffset(win32ResourcesOffset, resourceDataDir.VirtualAddress);
ReuseIfPossible(rsrcSection, win32Resources, resourceDataDir.VirtualAddress, resourceDataDir.Size, DEFAULT_WIN32_RESOURCES_ALIGNMENT);
}
}
ReuseIfPossible(textSection, imageCor20Header, module.Metadata.PEImage.ImageNTHeaders.OptionalHeader.DataDirectories[14].VirtualAddress, module.Metadata.PEImage.ImageNTHeaders.OptionalHeader.DataDirectories[14].Size, DEFAULT_COR20HEADER_ALIGNMENT);
if ((module.Metadata.ImageCor20Header.Flags & ComImageFlags.StrongNameSigned) != 0)
ReuseIfPossible(textSection, strongNameSignature, module.Metadata.ImageCor20Header.StrongNameSignature.VirtualAddress, module.Metadata.ImageCor20Header.StrongNameSignature.Size, DEFAULT_STRONGNAMESIG_ALIGNMENT);
ReuseIfPossible(textSection, netResources, module.Metadata.ImageCor20Header.Resources.VirtualAddress, module.Metadata.ImageCor20Header.Resources.Size, DEFAULT_NETRESOURCES_ALIGNMENT);
if (methodBodies.ReusedAllMethodBodyLocations)
textSection.Remove(methodBodies);
var debugDataDir = peImage.ImageNTHeaders.OptionalHeader.DataDirectories[6];
if (debugDataDir.VirtualAddress != 0 && debugDataDir.Size != 0 && TryGetRealDebugDirectorySize(peImage, out uint realDebugDirSize))
ReuseIfPossible(textSection, debugDirectory, debugDataDir.VirtualAddress, realDebugDirSize, DebugDirectory.DEFAULT_DEBUGDIRECTORY_ALIGNMENT);
}
if (constants.IsEmpty)
textSection.Remove(constants);
if (netResources.IsEmpty)
textSection.Remove(netResources);
if (textSection.IsEmpty)
sections.Remove(textSection);
if (rsrcSection is not null && rsrcSection.IsEmpty) {
sections.Remove(rsrcSection);
rsrcSection = null;
}
var headerSection = CreateHeaderSection(out var extraHeaderData);
var chunks = new List<IChunk>();
uint headerLen;
if (extraHeaderData is not null) {
var list = new ChunkList<IChunk>();
list.Add(headerSection, 1);
list.Add(extraHeaderData, 1);
chunks.Add(list);
headerLen = headerSection.GetVirtualSize() + extraHeaderData.GetVirtualSize();
}
else {
chunks.Add(headerSection);
headerLen = headerSection.GetVirtualSize();
}
foreach (var origSection in origSections)
chunks.Add(origSection.Chunk);
foreach (var section in sections)
chunks.Add(section);
if (extraData is not null)
chunks.Add(extraData);
CalculateRvasAndFileOffsets(chunks, 0, 0, peImage.ImageNTHeaders.OptionalHeader.FileAlignment, peImage.ImageNTHeaders.OptionalHeader.SectionAlignment);
if (reusedChunks.Count > 0 || methodBodies.HasReusedMethods) {
methodBodies.InitializeReusedMethodBodies(GetNewFileOffset);
foreach (var info in reusedChunks) {
var offset = GetNewFileOffset(info.RVA);
info.Chunk.SetOffset(offset, info.RVA);
}
}
metadata.UpdateMethodAndFieldRvas();
foreach (var section in origSections) {
if (section.Chunk.RVA != section.PESection.VirtualAddress)
throw new ModuleWriterException("Invalid section RVA");
}
OnWriterEvent(ModuleWriterEvent.EndCalculateRvasAndFileOffsets);
OnWriterEvent(ModuleWriterEvent.BeginWriteChunks);
var writer = new DataWriter(destStream);
WriteChunks(writer, chunks, 0, peImage.ImageNTHeaders.OptionalHeader.FileAlignment);
long imageLength = writer.Position - destStreamBaseOffset;
if (reusedChunks.Count > 0 || methodBodies.HasReusedMethods) {
var pos = writer.Position;
foreach (var info in reusedChunks) {
Debug.Assert(info.Chunk.RVA == info.RVA);
if (info.Chunk.RVA != info.RVA)
throw new InvalidOperationException();
writer.Position = destStreamBaseOffset + (uint)info.Chunk.FileOffset;
info.Chunk.VerifyWriteTo(writer);
}
methodBodies.WriteReusedMethodBodies(writer, destStreamBaseOffset);
writer.Position = pos;
}
var sectionSizes = new SectionSizes(peImage.ImageNTHeaders.OptionalHeader.FileAlignment, peImage.ImageNTHeaders.OptionalHeader.SectionAlignment, headerLen, GetSectionSizeInfos);
UpdateHeaderFields(writer, entryPointIsManagedOrNoEntryPoint, entryPointToken, ref sectionSizes);
OnWriterEvent(ModuleWriterEvent.EndWriteChunks);
OnWriterEvent(ModuleWriterEvent.BeginStrongNameSign);
if (Options.StrongNameKey is not null)
StrongNameSign((long)strongNameSignature.FileOffset);
OnWriterEvent(ModuleWriterEvent.EndStrongNameSign);
OnWriterEvent(ModuleWriterEvent.BeginWritePEChecksum);
if (Options.AddCheckSum) {
destStream.Position = destStreamBaseOffset;
uint newCheckSum = destStream.CalculatePECheckSum(imageLength, checkSumOffset);
writer.Position = checkSumOffset;
writer.WriteUInt32(newCheckSum);
}
OnWriterEvent(ModuleWriterEvent.EndWritePEChecksum);
return imageLength;
}
static bool TryGetRealDebugDirectorySize(IPEImage peImage, out uint realSize) {
realSize = 0;
if (peImage.ImageDebugDirectories.Count == 0)
return false;
var dirs = new List<ImageDebugDirectory>(peImage.ImageDebugDirectories);
dirs.Sort((a, b) => a.AddressOfRawData.CompareTo(b.AddressOfRawData));
var debugDataDir = peImage.ImageNTHeaders.OptionalHeader.DataDirectories[6];
var prevEnd = (uint)debugDataDir.VirtualAddress + debugDataDir.Size;
for (int i = 0; i < dirs.Count; i++) {
uint prevEndAligned = (prevEnd + 3) & ~3U;
var dir = dirs[i];
if (dir.AddressOfRawData == 0 || dir.SizeOfData == 0)
continue;
if (!(prevEnd <= (uint)dir.AddressOfRawData && (uint)dir.AddressOfRawData <= prevEndAligned))
return false;
prevEnd = (uint)dir.AddressOfRawData + dir.SizeOfData;
}
realSize = prevEnd - (uint)debugDataDir.VirtualAddress;
return true;
}
/// <summary>
/// <c>true</c> if image is 64-bit
/// </summary>
bool Is64Bit() => peImage.ImageNTHeaders.OptionalHeader is ImageOptionalHeader64;
Characteristics GetCharacteristics() {
var ch = module.Characteristics;
if (Is64Bit())
ch &= ~Characteristics.Bit32Machine;
else
ch |= Characteristics.Bit32Machine;
if (Options.IsExeFile)
ch &= ~Characteristics.Dll;
else
ch |= Characteristics.Dll;
return ch;
}
/// <summary>
/// Updates the PE header and COR20 header fields that need updating. All sections are
/// also updated, and the new ones are added.
/// </summary>
void UpdateHeaderFields(DataWriter writer, bool entryPointIsManagedOrNoEntryPoint, uint entryPointToken, ref SectionSizes sectionSizes) {
long fileHeaderOffset = destStreamBaseOffset + (long)peImage.ImageNTHeaders.FileHeader.StartOffset;
long optionalHeaderOffset = destStreamBaseOffset + (long)peImage.ImageNTHeaders.OptionalHeader.StartOffset;
long sectionsOffset = destStreamBaseOffset + (long)peImage.ImageSectionHeaders[0].StartOffset;
long dataDirOffset = destStreamBaseOffset + (long)peImage.ImageNTHeaders.OptionalHeader.EndOffset - 16 * 8;
long cor20Offset = destStreamBaseOffset + (long)imageCor20Header.FileOffset;
// Update PE file header
var peOptions = Options.PEHeadersOptions;
writer.Position = fileHeaderOffset;
writer.WriteUInt16((ushort)(peOptions.Machine ?? module.Machine));
writer.WriteUInt16((ushort)(origSections.Count + sections.Count));
WriteUInt32(writer, peOptions.TimeDateStamp);
WriteUInt32(writer, peOptions.PointerToSymbolTable);
WriteUInt32(writer, peOptions.NumberOfSymbols);
writer.Position += 2; // sizeof(SizeOfOptionalHeader)
writer.WriteUInt16((ushort)(peOptions.Characteristics ?? GetCharacteristics()));
// Update optional header
writer.Position = optionalHeaderOffset;
bool is32BitOptionalHeader = peImage.ImageNTHeaders.OptionalHeader is ImageOptionalHeader32;
if (is32BitOptionalHeader) {
writer.Position += 2;
WriteByte(writer, peOptions.MajorLinkerVersion);
WriteByte(writer, peOptions.MinorLinkerVersion);
writer.WriteUInt32(sectionSizes.SizeOfCode);
writer.WriteUInt32(sectionSizes.SizeOfInitdData);
writer.WriteUInt32(sectionSizes.SizeOfUninitdData);
writer.Position += 4; // EntryPoint
writer.WriteUInt32(sectionSizes.BaseOfCode);
writer.WriteUInt32(sectionSizes.BaseOfData);
WriteUInt32(writer, peOptions.ImageBase);
writer.Position += 8; // SectionAlignment, FileAlignment
WriteUInt16(writer, peOptions.MajorOperatingSystemVersion);
WriteUInt16(writer, peOptions.MinorOperatingSystemVersion);
WriteUInt16(writer, peOptions.MajorImageVersion);
WriteUInt16(writer, peOptions.MinorImageVersion);
WriteUInt16(writer, peOptions.MajorSubsystemVersion);
WriteUInt16(writer, peOptions.MinorSubsystemVersion);
WriteUInt32(writer, peOptions.Win32VersionValue);
writer.WriteUInt32(sectionSizes.SizeOfImage);
writer.WriteUInt32(sectionSizes.SizeOfHeaders);
checkSumOffset = writer.Position;
writer.WriteInt32(0); // CheckSum
WriteUInt16(writer, peOptions.Subsystem);
WriteUInt16(writer, peOptions.DllCharacteristics);
WriteUInt32(writer, peOptions.SizeOfStackReserve);
WriteUInt32(writer, peOptions.SizeOfStackCommit);
WriteUInt32(writer, peOptions.SizeOfHeapReserve);
WriteUInt32(writer, peOptions.SizeOfHeapCommit);
WriteUInt32(writer, peOptions.LoaderFlags);
WriteUInt32(writer, peOptions.NumberOfRvaAndSizes);
}
else {
writer.Position += 2;
WriteByte(writer, peOptions.MajorLinkerVersion);
WriteByte(writer, peOptions.MinorLinkerVersion);
writer.WriteUInt32(sectionSizes.SizeOfCode);
writer.WriteUInt32(sectionSizes.SizeOfInitdData);
writer.WriteUInt32(sectionSizes.SizeOfUninitdData);
writer.Position += 4; // EntryPoint
writer.WriteUInt32(sectionSizes.BaseOfCode);
WriteUInt64(writer, peOptions.ImageBase);
writer.Position += 8; // SectionAlignment, FileAlignment
WriteUInt16(writer, peOptions.MajorOperatingSystemVersion);
WriteUInt16(writer, peOptions.MinorOperatingSystemVersion);
WriteUInt16(writer, peOptions.MajorImageVersion);
WriteUInt16(writer, peOptions.MinorImageVersion);
WriteUInt16(writer, peOptions.MajorSubsystemVersion);
WriteUInt16(writer, peOptions.MinorSubsystemVersion);
WriteUInt32(writer, peOptions.Win32VersionValue);
writer.WriteUInt32(sectionSizes.SizeOfImage);
writer.WriteUInt32(sectionSizes.SizeOfHeaders);
checkSumOffset = writer.Position;
writer.WriteInt32(0); // CheckSum
WriteUInt16(writer, peOptions.Subsystem ?? GetSubsystem());
WriteUInt16(writer, peOptions.DllCharacteristics ?? module.DllCharacteristics);
WriteUInt64(writer, peOptions.SizeOfStackReserve);
WriteUInt64(writer, peOptions.SizeOfStackCommit);
WriteUInt64(writer, peOptions.SizeOfHeapReserve);
WriteUInt64(writer, peOptions.SizeOfHeapCommit);
WriteUInt32(writer, peOptions.LoaderFlags);
WriteUInt32(writer, peOptions.NumberOfRvaAndSizes);
}
// Update Win32 resources data directory, if we wrote a new one
if (win32Resources is not null) {
writer.Position = dataDirOffset + 2 * 8;
writer.WriteDataDirectory(win32Resources);
}
// Clear the security descriptor directory
writer.Position = dataDirOffset + 4 * 8;
writer.WriteDataDirectory(null);
// Write a new debug directory
writer.Position = dataDirOffset + 6 * 8;
writer.WriteDebugDirectory(debugDirectory);
// Write a new Metadata data directory
writer.Position = dataDirOffset + 14 * 8;
writer.WriteDataDirectory(imageCor20Header);
// Update old sections, and add new sections
writer.Position = sectionsOffset;
foreach (var section in origSections) {
writer.Position += 0x14;
writer.WriteUInt32((uint)section.Chunk.FileOffset); // PointerToRawData
writer.Position += 0x10;
}
foreach (var section in sections)
section.WriteHeaderTo(writer, peImage.ImageNTHeaders.OptionalHeader.FileAlignment, peImage.ImageNTHeaders.OptionalHeader.SectionAlignment, (uint)section.RVA);
// Write the .NET header
writer.Position = cor20Offset;
writer.WriteInt32(0x48); // cb
WriteUInt16(writer, Options.Cor20HeaderOptions.MajorRuntimeVersion);
WriteUInt16(writer, Options.Cor20HeaderOptions.MinorRuntimeVersion);
writer.WriteDataDirectory(metadata);
writer.WriteUInt32((uint)GetComImageFlags(entryPointIsManagedOrNoEntryPoint));
writer.WriteUInt32(entryPointToken);
writer.WriteDataDirectory(netResources);
writer.WriteDataDirectory(strongNameSignature);
WriteDataDirectory(writer, module.Metadata.ImageCor20Header.CodeManagerTable);
WriteDataDirectory(writer, module.Metadata.ImageCor20Header.VTableFixups);
WriteDataDirectory(writer, module.Metadata.ImageCor20Header.ExportAddressTableJumps);
WriteDataDirectory(writer, module.Metadata.ImageCor20Header.ManagedNativeHeader);
UpdateVTableFixups(writer);
}
static void WriteDataDirectory(DataWriter writer, ImageDataDirectory dataDir) {
writer.WriteUInt32((uint)dataDir.VirtualAddress);
writer.WriteUInt32(dataDir.Size);
}
static void WriteByte(DataWriter writer, byte? value) {
if (value is null)
writer.Position++;
else
writer.WriteByte(value.Value);
}
static void WriteUInt16(DataWriter writer, ushort? value) {
if (value is null)
writer.Position += 2;
else
writer.WriteUInt16(value.Value);
}
static void WriteUInt16(DataWriter writer, Subsystem? value) {
if (value is null)
writer.Position += 2;
else
writer.WriteUInt16((ushort)value.Value);
}
static void WriteUInt16(DataWriter writer, DllCharacteristics? value) {
if (value is null)
writer.Position += 2;
else
writer.WriteUInt16((ushort)value.Value);
}
static void WriteUInt32(DataWriter writer, uint? value) {
if (value is null)
writer.Position += 4;
else
writer.WriteUInt32(value.Value);
}
static void WriteUInt32(DataWriter writer, ulong? value) {
if (value is null)
writer.Position += 4;
else
writer.WriteUInt32((uint)value.Value);
}
static void WriteUInt64(DataWriter writer, ulong? value) {
if (value is null)
writer.Position += 8;
else
writer.WriteUInt64(value.Value);
}
ComImageFlags GetComImageFlags(bool isManagedEntryPoint) {
var flags = Options.Cor20HeaderOptions.Flags ?? module.Cor20HeaderFlags;
if (Options.Cor20HeaderOptions.EntryPoint is not null)
return flags;
if (isManagedEntryPoint)
return flags & ~ComImageFlags.NativeEntryPoint;
return flags | ComImageFlags.NativeEntryPoint;
}
Subsystem GetSubsystem() {
if (module.Kind == ModuleKind.Windows)
return Subsystem.WindowsGui;
return Subsystem.WindowsCui;
}
/// <summary>
/// Converts <paramref name="rva"/> to a file offset in the destination stream
/// </summary>
/// <param name="rva">RVA</param>
long ToWriterOffset(RVA rva) {
if (rva == 0)
return 0;
foreach (var sect in origSections) {
var section = sect.PESection;
if (section.VirtualAddress <= rva && rva < section.VirtualAddress + Math.Max(section.VirtualSize, section.SizeOfRawData))
return destStreamBaseOffset + (long)sect.Chunk.FileOffset + (rva - section.VirtualAddress);
}
return 0;
}
IEnumerable<SectionSizeInfo> GetSectionSizeInfos() {
foreach (var section in origSections)
yield return new SectionSizeInfo(section.Chunk.GetVirtualSize(), section.PESection.Characteristics);
foreach (var section in sections)
yield return new SectionSizeInfo(section.GetVirtualSize(), section.Characteristics);
}
void UpdateVTableFixups(DataWriter writer) {
var vtableFixups = module.VTableFixups;
if (vtableFixups is null || vtableFixups.VTables.Count == 0)
return;
writer.Position = ToWriterOffset(vtableFixups.RVA);
if (writer.Position == 0) {
Error("Could not convert RVA to file offset");
return;
}
foreach (var vtable in vtableFixups) {
if (vtable.Methods.Count > ushort.MaxValue)
throw new ModuleWriterException("Too many methods in vtable");
writer.WriteUInt32((uint)vtable.RVA);
writer.WriteUInt16((ushort)vtable.Methods.Count);
writer.WriteUInt16((ushort)vtable.Flags);
long pos = writer.Position;
writer.Position = ToWriterOffset(vtable.RVA);
if (writer.Position == 0) {
if (vtable.RVA != 0 || vtable.Methods.Count > 0)
Error("Could not convert RVA to file offset");
}
else {
var methods = vtable.Methods;
int count = methods.Count;
for (int i = 0; i < count; i++) {
var method = methods[i];
writer.WriteUInt32(GetMethodToken(method));
if (vtable.Is64Bit)
writer.WriteInt32(0);
}
}
writer.Position = pos;
}
}
uint GetMethodToken(IMethod method) {
if (method is MethodDef md)
return new MDToken(Table.Method, metadata.GetRid(md)).Raw;
if (method is MemberRef mr)
return new MDToken(Table.MemberRef, metadata.GetRid(mr)).Raw;
if (method is MethodSpec ms)
return new MDToken(Table.MethodSpec, metadata.GetRid(ms)).Raw;
if (method is null)
return 0;
Error("Invalid VTable method type: {0}", method.GetType());
return 0;
}
/// <summary>
/// Gets the entry point
/// </summary>
/// <param name="ep">Updated with entry point (either a token or RVA of native method)</param>
/// <returns><c>true</c> if it's a managed entry point or there's no entry point,
/// <c>false</c> if it's a native entry point</returns>
bool GetEntryPoint(out uint ep) {
var tok = Options.Cor20HeaderOptions.EntryPoint;
if (tok is not null) {
ep = tok.Value;
return ep == 0 || ((Options.Cor20HeaderOptions.Flags ?? 0) & ComImageFlags.NativeEntryPoint) == 0;
}
if (module.ManagedEntryPoint is MethodDef epMethod) {
ep = new MDToken(Table.Method, metadata.GetRid(epMethod)).Raw;
return true;
}
if (module.ManagedEntryPoint is FileDef file) {
ep = new MDToken(Table.File, metadata.GetRid(file)).Raw;
return true;
}
ep = (uint)module.NativeEntryPoint;
return ep == 0;
}
}
}