|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Internal;
using System.Reflection.Metadata;
namespace System.Reflection.PortableExecutable
{
public abstract class PEBuilder
{
public PEHeaderBuilder Header { get; }
public Func<IEnumerable<Blob>, BlobContentId> IdProvider { get; }
public bool IsDeterministic { get; }
private readonly Lazy<ImmutableArray<Section>> _lazySections;
private Blob _lazyChecksum;
protected readonly struct Section
{
public readonly string Name;
public readonly SectionCharacteristics Characteristics;
public Section(string name, SectionCharacteristics characteristics)
{
if (name is null)
{
Throw.ArgumentNull(nameof(name));
}
Name = name;
Characteristics = characteristics;
}
}
private readonly struct SerializedSection
{
public readonly BlobBuilder Builder;
public readonly string Name;
public readonly SectionCharacteristics Characteristics;
public readonly int RelativeVirtualAddress;
public readonly int SizeOfRawData;
public readonly int PointerToRawData;
public SerializedSection(BlobBuilder builder, string name, SectionCharacteristics characteristics, int relativeVirtualAddress, int sizeOfRawData, int pointerToRawData)
{
Name = name;
Characteristics = characteristics;
Builder = builder;
RelativeVirtualAddress = relativeVirtualAddress;
SizeOfRawData = sizeOfRawData;
PointerToRawData = pointerToRawData;
}
public int VirtualSize => Builder.Count;
}
protected PEBuilder(PEHeaderBuilder header, Func<IEnumerable<Blob>, BlobContentId>? deterministicIdProvider)
{
if (header is null)
{
Throw.ArgumentNull(nameof(header));
}
IdProvider = deterministicIdProvider ?? BlobContentId.GetTimeBasedProvider();
IsDeterministic = deterministicIdProvider != null;
Header = header;
_lazySections = new Lazy<ImmutableArray<Section>>(CreateSections);
}
protected ImmutableArray<Section> GetSections()
{
var sections = _lazySections.Value;
if (sections.IsDefault)
{
throw new InvalidOperationException(SR.Format(SR.MustNotReturnNull, nameof(CreateSections)));
}
return sections;
}
protected abstract ImmutableArray<Section> CreateSections();
protected abstract BlobBuilder SerializeSection(string name, SectionLocation location);
protected internal abstract PEDirectoriesBuilder GetDirectories();
public BlobContentId Serialize(BlobBuilder builder)
{
// Define and serialize sections in two steps.
// We need to know about all sections before serializing them.
var serializedSections = SerializeSections();
// The positions and sizes of directories are calculated during section serialization.
var directories = GetDirectories();
Blob stampFixup;
WritePESignature(builder);
WriteCoffHeader(builder, serializedSections, out stampFixup);
WritePEHeader(builder, directories, serializedSections);
WriteSectionHeaders(builder, serializedSections);
builder.Align(Header.FileAlignment);
foreach (var section in serializedSections)
{
builder.LinkSuffix(section.Builder);
builder.Align(Header.FileAlignment);
}
var contentId = IdProvider(builder.GetBlobs());
// patch timestamp in COFF header:
var stampWriter = new BlobWriter(stampFixup);
stampWriter.WriteUInt32(contentId.Stamp);
Debug.Assert(stampWriter.RemainingBytes == 0);
return contentId;
}
private ImmutableArray<SerializedSection> SerializeSections()
{
var sections = GetSections();
var result = ImmutableArray.CreateBuilder<SerializedSection>(sections.Length);
int sizeOfPeHeaders = Header.ComputeSizeOfPEHeaders(sections.Length);
var nextRva = BitArithmetic.Align(sizeOfPeHeaders, Header.SectionAlignment);
var nextPointer = BitArithmetic.Align(sizeOfPeHeaders, Header.FileAlignment);
foreach (var section in sections)
{
var builder = SerializeSection(section.Name, new SectionLocation(nextRva, nextPointer));
var serialized = new SerializedSection(
builder,
section.Name,
section.Characteristics,
relativeVirtualAddress: nextRva,
sizeOfRawData: BitArithmetic.Align(builder.Count, Header.FileAlignment),
pointerToRawData: nextPointer);
result.Add(serialized);
nextRva = BitArithmetic.Align(serialized.RelativeVirtualAddress + serialized.VirtualSize, Header.SectionAlignment);
nextPointer = serialized.PointerToRawData + serialized.SizeOfRawData;
}
return result.MoveToImmutable();
}
private static unsafe void WritePESignature(BlobBuilder builder)
{
// MS-DOS stub (128 bytes)
ReadOnlySpan<byte> header = DosHeader;
Debug.Assert(DosHeader.Length == DosHeaderSize);
fixed (byte* ptr = header)
{
builder.WriteBytes(ptr, header.Length);
}
// PE Signature "PE\0\0"
builder.WriteUInt32(PEHeaders.PESignature);
}
internal const int DosHeaderSize = 0x80;
private static ReadOnlySpan<byte> DosHeader => // DosHeaderSize
[
0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, // NT Header offset (0x80 == DosHeader.Length)
0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd,
0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68,
0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72,
0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f,
0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e,
0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20,
0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a,
0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
];
private void WriteCoffHeader(BlobBuilder builder, ImmutableArray<SerializedSection> sections, out Blob stampFixup)
{
// Machine
builder.WriteUInt16((ushort)(Header.Machine == 0 ? Machine.I386 : Header.Machine));
// NumberOfSections
builder.WriteUInt16((ushort)sections.Length);
// TimeDateStamp:
stampFixup = builder.ReserveBytes(sizeof(uint));
// PointerToSymbolTable (TODO: not supported):
// The file pointer to the COFF symbol table, or zero if no COFF symbol table is present.
// This value should be zero for a PE image.
builder.WriteUInt32(0);
// NumberOfSymbols (TODO: not supported):
// The number of entries in the symbol table. This data can be used to locate the string table,
// which immediately follows the symbol table. This value should be zero for a PE image.
builder.WriteUInt32(0);
// SizeOfOptionalHeader:
// The size of the optional header, which is required for executable files but not for object files.
// This value should be zero for an object file (TODO).
builder.WriteUInt16((ushort)PEHeader.Size(Header.Is32Bit));
// Characteristics
builder.WriteUInt16((ushort)Header.ImageCharacteristics);
}
private void WritePEHeader(BlobBuilder builder, PEDirectoriesBuilder directories, ImmutableArray<SerializedSection> sections)
{
builder.WriteUInt16((ushort)(Header.Is32Bit ? PEMagic.PE32 : PEMagic.PE32Plus));
builder.WriteByte(Header.MajorLinkerVersion);
builder.WriteByte(Header.MinorLinkerVersion);
// SizeOfCode:
builder.WriteUInt32((uint)SumRawDataSizes(sections, SectionCharacteristics.ContainsCode));
// SizeOfInitializedData:
builder.WriteUInt32((uint)SumRawDataSizes(sections, SectionCharacteristics.ContainsInitializedData));
// SizeOfUninitializedData:
builder.WriteUInt32((uint)SumRawDataSizes(sections, SectionCharacteristics.ContainsUninitializedData));
// AddressOfEntryPoint:
builder.WriteUInt32((uint)directories.AddressOfEntryPoint);
// BaseOfCode:
int codeSectionIndex = IndexOfSection(sections, SectionCharacteristics.ContainsCode);
builder.WriteUInt32((uint)(codeSectionIndex != -1 ? sections[codeSectionIndex].RelativeVirtualAddress : 0));
if (Header.Is32Bit)
{
// BaseOfData:
int dataSectionIndex = IndexOfSection(sections, SectionCharacteristics.ContainsInitializedData);
builder.WriteUInt32((uint)(dataSectionIndex != -1 ? sections[dataSectionIndex].RelativeVirtualAddress : 0));
builder.WriteUInt32((uint)Header.ImageBase);
}
else
{
builder.WriteUInt64(Header.ImageBase);
}
// NT additional fields:
builder.WriteUInt32((uint)Header.SectionAlignment);
builder.WriteUInt32((uint)Header.FileAlignment);
builder.WriteUInt16(Header.MajorOperatingSystemVersion);
builder.WriteUInt16(Header.MinorOperatingSystemVersion);
builder.WriteUInt16(Header.MajorImageVersion);
builder.WriteUInt16(Header.MinorImageVersion);
builder.WriteUInt16(Header.MajorSubsystemVersion);
builder.WriteUInt16(Header.MinorSubsystemVersion);
// Win32VersionValue (reserved, should be 0)
builder.WriteUInt32(0);
// SizeOfImage:
var lastSection = sections[sections.Length - 1];
builder.WriteUInt32((uint)BitArithmetic.Align(lastSection.RelativeVirtualAddress + lastSection.VirtualSize, Header.SectionAlignment));
// SizeOfHeaders:
builder.WriteUInt32((uint)BitArithmetic.Align(Header.ComputeSizeOfPEHeaders(sections.Length), Header.FileAlignment));
// Checksum:
// Shall be zero for strong name signing.
_lazyChecksum = builder.ReserveBytes(sizeof(uint));
new BlobWriter(_lazyChecksum).WriteUInt32(0);
builder.WriteUInt16((ushort)Header.Subsystem);
builder.WriteUInt16((ushort)Header.DllCharacteristics);
if (Header.Is32Bit)
{
builder.WriteUInt32((uint)Header.SizeOfStackReserve);
builder.WriteUInt32((uint)Header.SizeOfStackCommit);
builder.WriteUInt32((uint)Header.SizeOfHeapReserve);
builder.WriteUInt32((uint)Header.SizeOfHeapCommit);
}
else
{
builder.WriteUInt64(Header.SizeOfStackReserve);
builder.WriteUInt64(Header.SizeOfStackCommit);
builder.WriteUInt64(Header.SizeOfHeapReserve);
builder.WriteUInt64(Header.SizeOfHeapCommit);
}
// LoaderFlags
builder.WriteUInt32(0);
// The number of data-directory entries in the remainder of the header.
builder.WriteUInt32(16);
// directory entries:
builder.WriteUInt32((uint)directories.ExportTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.ExportTable.Size);
builder.WriteUInt32((uint)directories.ImportTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.ImportTable.Size);
builder.WriteUInt32((uint)directories.ResourceTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.ResourceTable.Size);
builder.WriteUInt32((uint)directories.ExceptionTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.ExceptionTable.Size);
// Authenticode CertificateTable directory. Shall be zero before the PE is signed.
builder.WriteUInt32(0);
builder.WriteUInt32(0);
builder.WriteUInt32((uint)directories.BaseRelocationTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.BaseRelocationTable.Size);
builder.WriteUInt32((uint)directories.DebugTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.DebugTable.Size);
builder.WriteUInt32((uint)directories.CopyrightTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.CopyrightTable.Size);
builder.WriteUInt32((uint)directories.GlobalPointerTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.GlobalPointerTable.Size);
builder.WriteUInt32((uint)directories.ThreadLocalStorageTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.ThreadLocalStorageTable.Size);
builder.WriteUInt32((uint)directories.LoadConfigTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.LoadConfigTable.Size);
builder.WriteUInt32((uint)directories.BoundImportTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.BoundImportTable.Size);
builder.WriteUInt32((uint)directories.ImportAddressTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.ImportAddressTable.Size);
builder.WriteUInt32((uint)directories.DelayImportTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.DelayImportTable.Size);
builder.WriteUInt32((uint)directories.CorHeaderTable.RelativeVirtualAddress);
builder.WriteUInt32((uint)directories.CorHeaderTable.Size);
// Reserved, should be 0
builder.WriteUInt64(0);
}
private static void WriteSectionHeaders(BlobBuilder builder, ImmutableArray<SerializedSection> serializedSections)
{
foreach (var serializedSection in serializedSections)
{
WriteSectionHeader(builder, serializedSection);
}
}
private static void WriteSectionHeader(BlobBuilder builder, SerializedSection serializedSection)
{
if (serializedSection.VirtualSize == 0)
{
return;
}
for (int j = 0, m = serializedSection.Name.Length; j < 8; j++)
{
if (j < m)
{
builder.WriteByte((byte)serializedSection.Name[j]);
}
else
{
builder.WriteByte(0);
}
}
builder.WriteUInt32((uint)serializedSection.VirtualSize);
builder.WriteUInt32((uint)serializedSection.RelativeVirtualAddress);
builder.WriteUInt32((uint)serializedSection.SizeOfRawData);
builder.WriteUInt32((uint)serializedSection.PointerToRawData);
// PointerToRelocations (TODO: not supported):
builder.WriteUInt32(0);
// PointerToLinenumbers (TODO: not supported):
builder.WriteUInt32(0);
// NumberOfRelocations (TODO: not supported):
builder.WriteUInt16(0);
// NumberOfLinenumbers (TODO: not supported):
builder.WriteUInt16(0);
builder.WriteUInt32((uint)serializedSection.Characteristics);
}
private static int IndexOfSection(ImmutableArray<SerializedSection> sections, SectionCharacteristics characteristics)
{
for (int i = 0; i < sections.Length; i++)
{
if ((sections[i].Characteristics & characteristics) == characteristics)
{
return i;
}
}
return -1;
}
private static int SumRawDataSizes(ImmutableArray<SerializedSection> sections, SectionCharacteristics characteristics)
{
int result = 0;
for (int i = 0; i < sections.Length; i++)
{
if ((sections[i].Characteristics & characteristics) == characteristics)
{
result += sections[i].SizeOfRawData;
}
}
return result;
}
// internal for testing
internal static IEnumerable<Blob> GetContentToSign(BlobBuilder peImage, int peHeadersSize, int peHeaderAlignment, Blob strongNameSignatureFixup)
{
// Signed content includes
// - PE header without its alignment padding
// - all sections including their alignment padding and excluding strong name signature blob
// PE specification:
// To calculate the PE image hash, Authenticode orders the sections that are specified in the section table
// by address range, then hashes the resulting sequence of bytes, passing over the exclusion ranges.
//
// Note that sections are by construction ordered by their address, so there is no need to reorder.
int remainingHeaderToSign = peHeadersSize;
int remainingHeader = BitArithmetic.Align(peHeadersSize, peHeaderAlignment);
foreach (var blob in peImage.GetBlobs())
{
int blobStart = blob.Start;
int blobLength = blob.Length;
while (blobLength > 0)
{
if (remainingHeader > 0)
{
int length;
if (remainingHeaderToSign > 0)
{
length = Math.Min(remainingHeaderToSign, blobLength);
yield return new Blob(blob.Buffer, blobStart, length);
remainingHeaderToSign -= length;
}
else
{
length = Math.Min(remainingHeader, blobLength);
}
remainingHeader -= length;
blobStart += length;
blobLength -= length;
}
else if (blob.Buffer == strongNameSignatureFixup.Buffer)
{
yield return GetPrefixBlob(new Blob(blob.Buffer, blobStart, blobLength), strongNameSignatureFixup);
yield return GetSuffixBlob(new Blob(blob.Buffer, blobStart, blobLength), strongNameSignatureFixup);
break;
}
else
{
yield return new Blob(blob.Buffer, blobStart, blobLength);
break;
}
}
}
}
// internal for testing
internal static Blob GetPrefixBlob(Blob container, Blob blob) => new Blob(container.Buffer, container.Start, blob.Start - container.Start);
internal static Blob GetSuffixBlob(Blob container, Blob blob) => new Blob(container.Buffer, blob.Start + blob.Length, container.Start + container.Length - blob.Start - blob.Length);
// internal for testing
internal static IEnumerable<Blob> GetContentToChecksum(BlobBuilder peImage, Blob checksumFixup)
{
foreach (var blob in peImage.GetBlobs())
{
if (blob.Buffer == checksumFixup.Buffer)
{
yield return GetPrefixBlob(blob, checksumFixup);
yield return GetSuffixBlob(blob, checksumFixup);
}
else
{
yield return blob;
}
}
}
internal void Sign(BlobBuilder peImage, Blob strongNameSignatureFixup, Func<IEnumerable<Blob>, byte[]> signatureProvider)
{
Debug.Assert(peImage != null);
Debug.Assert(signatureProvider != null);
int peHeadersSize = Header.ComputeSizeOfPEHeaders(GetSections().Length);
byte[] signature = signatureProvider(GetContentToSign(peImage, peHeadersSize, Header.FileAlignment, strongNameSignatureFixup));
// signature may be shorter (the rest of the reserved space is padding):
if (signature == null || signature.Length > strongNameSignatureFixup.Length)
{
throw new InvalidOperationException(SR.SignatureProviderReturnedInvalidSignature);
}
var writer = new BlobWriter(strongNameSignatureFixup);
writer.WriteBytes(signature);
// Calculate the checksum after the strong name signature has been written.
uint checksum = CalculateChecksum(peImage, _lazyChecksum);
new BlobWriter(_lazyChecksum).WriteUInt32(checksum);
}
// internal for testing
internal static uint CalculateChecksum(BlobBuilder peImage, Blob checksumFixup)
{
return CalculateChecksum(GetContentToChecksum(peImage, checksumFixup)) + (uint)peImage.Count;
}
private static unsafe uint CalculateChecksum(IEnumerable<Blob> blobs)
{
uint checksum = 0;
int pendingByte = -1;
foreach (var blob in blobs)
{
var segment = blob.GetBytes();
fixed (byte* arrayPtr = segment.Array)
{
Debug.Assert(segment.Count > 0);
byte* ptr = arrayPtr + segment.Offset;
byte* end = ptr + segment.Count;
if (pendingByte >= 0)
{
// little-endian encoding:
checksum = AggregateChecksum(checksum, (ushort)(*ptr << 8 | pendingByte));
ptr++;
}
if ((end - ptr) % 2 != 0)
{
end--;
pendingByte = *end;
}
else
{
pendingByte = -1;
}
while (ptr < end)
{
// little-endian encoding:
checksum = AggregateChecksum(checksum, (ushort)(ptr[1] << 8 | ptr[0]));
ptr += sizeof(ushort);
}
}
}
if (pendingByte >= 0)
{
checksum = AggregateChecksum(checksum, (ushort)pendingByte);
}
return checksum;
}
private static uint AggregateChecksum(uint checksum, ushort value)
{
uint sum = checksum + value;
return (sum >> 16) + unchecked((ushort)sum);
}
}
}
|