Skip to content

Commit

Permalink
Improve support for uncommon headers and PE directories layout
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Sep 28, 2024
1 parent a8831a1 commit 819c74c
Show file tree
Hide file tree
Showing 18 changed files with 440 additions and 150 deletions.
32 changes: 29 additions & 3 deletions src/LibObjectFile.Tests/PE/PEReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@

using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using LibObjectFile.Diagnostics;
using LibObjectFile.PE;
using VerifyMSTest;
using VerifyTests;

namespace LibObjectFile.Tests.PE;

Expand Down Expand Up @@ -49,4 +46,33 @@ public async Task TestPrinter(string name)
await Verifier.Verify(afterUpdateText).UseParameters(name).DisableRequireUniquePrefix();
}
}

[TestMethod]
public async Task TestTinyExe97Bytes()
{
// http://www.phreedom.org/research/tinype/
// TinyPE: The smallest possible PE file
// 97 bytes
byte[] data =
[
0x4D, 0x5A, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x01, 0x00, 0x6A, 0x2A, 0x58, 0xC3,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x01, 0x0B, 0x01, 0x08, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02
];

var stream = new MemoryStream();
stream.Write(data, 0, data.Length);
stream.Position = 0;

var peImage = PEFile.Read(stream);
var writer = new StringWriter();
peImage.Print(writer);
var afterReadText = writer.ToString();

await Verifier.Verify(afterReadText);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Data Directories
[12] = PEImportAddressTableDirectory Position = 0x00001E00, Size = 0x00000250, RVA = 0x00003000, VirtualSize = 0x00000250
[13] = PEDelayImportDirectory Position = 0x000029A8, Size = 0x00000040, RVA = 0x00003BA8, VirtualSize = 0x00000040
[14] = null
[15] = null

Section Headers
[00] .text PESection Position = 0x00000400, Size = 0x00001A00, RVA = 0x00001000, VirtualSize = 0x000019E9, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Data Directories
[12] = PEImportAddressTableDirectory Position = 0x00001600, Size = 0x000001F0, RVA = 0x00003000, VirtualSize = 0x000001F0
[13] = null
[14] = null
[15] = null

Section Headers
[00] .text PESection Position = 0x00000400, Size = 0x00001200, RVA = 0x00001000, VirtualSize = 0x000010C9, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Data Directories
[12] = PEImportAddressTableDirectory Position = 0x00001400, Size = 0x000000E8, RVA = 0x00002000, VirtualSize = 0x000000E8
[13] = null
[14] = null
[15] = null

Section Headers
[00] .text PESection Position = 0x00000400, Size = 0x00001000, RVA = 0x00001000, VirtualSize = 0x00000F18, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
DOS Header
Magic = DOS
ByteCountOnLastPage = 0x0
PageCount = 0x4550
RelocationCount = 0x0
SizeOfParagraphsHeader = 0x14C
MinExtraParagraphs = 0x1
MaxExtraParagraphs = 0x2A6A
InitialSSValue = 0xC358
InitialSPValue = 0x0
Checksum = 0x0
InitialIPValue = 0x0
InitialCSValue = 0x0
FileAddressRelocationTable = 0x4
OverlayNumber = 0x103
Reserved = 0x10B, 0x8, 0x4, 0x0
OEMIdentifier = 0x0
OEMInformation = 0x0
Reserved2 = 0x4, 0x0, 0xC, 0x0, 0x4, 0x0, 0xC, 0x0, 0x0, 0x40
FileAddressPEHeader = 0x4

DOS Stub
DosStub = 0 bytes

COFF Header
Machine = I386
NumberOfSections = 1
TimeDateStamp = 3277335146
PointerToSymbolTable = 0x0
NumberOfSymbols = 0
SizeOfOptionalHeader = 4
Characteristics = RelocsStripped, ExecutableImage, Bit32Machine

Optional Header
Magic = PE32
MajorLinkerVersion = 8
MinorLinkerVersion = 0
SizeOfCode = 0x4
SizeOfInitializedData = 0x0
SizeOfUninitializedData = 0x4
AddressOfEntryPoint = 0xC
BaseOfCode = 0x4
BaseOfData = 0xC
ImageBase = 0x0
SectionAlignment = 0x4
FileAlignment = 0x4
MajorOperatingSystemVersion = 4
MinorOperatingSystemVersion = 0
MajorImageVersion = 0
MinorImageVersion = 0
MajorSubsystemVersion = 4
MinorSubsystemVersion = 0
Win32VersionValue = 0x0
SizeOfImage = 0x68
SizeOfHeaders = 0x64
CheckSum = 0x0
Subsystem = WindowsGui
DllCharacteristics = 0
SizeOfStackReserve = 0x0
SizeOfStackCommit = 0x0
SizeOfHeapReserve = 0x0
SizeOfHeapCommit = 0x0
LoaderFlags = 0x0
NumberOfRvaAndSizes = 0x0

Section Headers
[00]  PESection Position = 0x0000000C, Size = 0x00000004, RVA = 0x0000000C, VirtualSize = 0x00000004, Characteristics = 0x00000004 (TypeGroup)

Sections
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[00]  PESection Position = 0x0000000C, Size = 0x00000004, RVA = 0x0000000C, VirtualSize = 0x00000004, Characteristics = 0x00000004 (TypeGroup)

[00] PEStreamSectionData Position = 0x0000000C, Size = 0x00000004, RVA = 0x0000000C, VirtualSize = 0x00000004

6 changes: 4 additions & 2 deletions src/LibObjectFile/Diagnostics/DiagnosticId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,10 @@ public enum DiagnosticId
PE_ERR_TooManySections = 3012,
PE_ERR_FileAlignmentNotPowerOfTwo = 3013,
PE_ERR_SectionAlignmentNotPowerOfTwo = 3014,
PE_ERR_SectionAlignmentLessThanFileAlignment = 315,

PE_ERR_SectionAlignmentLessThanFileAlignment = 3015,
PE_ERR_InvalidPEHeaderPosition = 3016,
PE_ERR_InvalidNumberOfDataDirectories = 3017,

// PE Exception directory
PE_ERR_InvalidExceptionDirectory_Entries = 3100,
PE_ERR_InvalidExceptionDirectory_Entry = 3101,
Expand Down
4 changes: 2 additions & 2 deletions src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal static PEDataDirectory Create(PEDataDirectoryKind kind, bool is32Bits)
PEDataDirectoryKind.DelayImport => new PEDelayImportDirectory(),
PEDataDirectoryKind.ImportAddressTable => new PEImportAddressTableDirectory(),
PEDataDirectoryKind.ClrMetadata => new PEClrMetadata(),
_ => throw new ArgumentOutOfRangeException(nameof(kind))
_ => new PEUnknownDirectory((int)kind)
};
}
}
}
133 changes: 76 additions & 57 deletions src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace LibObjectFile.PE;
Expand All @@ -17,20 +18,81 @@ namespace LibObjectFile.PE;
[DebuggerDisplay($"{nameof(PEDirectoryTable)} {nameof(Count)} = {{{nameof(Count)}}}")]
public sealed class PEDirectoryTable : IEnumerable<PEDataDirectory>
{
private InternalArray _entries;
private PEObjectBase?[] _entries;
private int _count;

internal PEDirectoryTable()
{
_entries = [];
}

public PEObjectBase? this[PEDataDirectoryKind kind] => _entries[(int)kind];
/// <summary>
/// Gets the directory entry at the specified index.
/// </summary>
/// <param name="index">The index of the directory entry to get.</param>
/// <returns>The directory entry at the specified index.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the index is out of range.</exception>
public PEObjectBase? this[int index]
{
get
{
if (index < 0 || index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _entries[index];
}
}

/// <summary>
/// Gets the number of directory entries in the array.
/// Gets the directory entry of the specified kind. Must be within the bounds of <see cref="Count"/>.
/// </summary>
public int Count => _count;

/// <param name="kind">The kind of directory entry to get.</param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the kind is out of range.</exception>
public PEObjectBase? this[PEDataDirectoryKind kind]
{
get
{
int index = (int)(ushort)kind;
if (index < 0 || index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(kind));
}
return _entries[(int)kind];
}
}

/// <summary>
/// Gets the maximum number of directory entries in the array.
/// </summary>
public int Count
{
get => _count;

set
{
ArgumentOutOfRangeException.ThrowIfLessThan(value, 0);

var previousCount = _count;
// If the count is reduced, we need to check that all entries are null after
for (int i = value; i < previousCount; i++)
{
if (_entries[i] is not null)
{
throw new ArgumentOutOfRangeException(nameof(value), $"A non null directory entry was found at index {i}. This directory entry must be removed before setting a count of {value}");
}
}

if (_entries.Length < value)
{
Array.Resize(ref _entries, value);
}

_count = value;
}
}

/// <summary>
/// Gets the export directory information from the PE file.
/// </summary>
Expand Down Expand Up @@ -112,60 +174,23 @@ internal PEDirectoryTable()
[EditorBrowsable(EditorBrowsableState.Never)]
public Enumerator GetEnumerator() => new(this);

internal void Set(PESecurityCertificateDirectory? directory)
{
var kind = PEDataDirectoryKind.SecurityCertificate;
ref var entry = ref _entries[(int)kind];
var previousEntry = entry;
entry = directory;
internal void Set(PESecurityCertificateDirectory? directory) => Set(PEDataDirectoryKind.SecurityCertificate, directory);

if (previousEntry is not null)
{
_count--;
}
internal void Set(PEDataDirectoryKind kind, PEObjectBase? directory) => Set((int)kind, directory);

if (directory is not null)
{
_count++;
}
}

internal void Set(PEDataDirectoryKind kind, PEDataDirectory? directory)
internal void Set(int index, PEObjectBase? directory)
{
ref var entry = ref _entries[(int)kind];
var previousEntry = entry;
entry = directory;

if (previousEntry is not null)
if (index >= Count)
{
_count--;
throw new ArgumentOutOfRangeException(nameof(index), $"The directory entry only accepts {Count} entries. Set the count explicitly to allow more entries.");
}

if (directory is not null)
{
_count++;
}
_entries[index] = directory;
}

internal int CalculateNumberOfEntries()
{
int count = 0;
ReadOnlySpan<PEObjectBase?> span = _entries;
for(int i = 0; i < span.Length; i++)
{
if (_entries[i] is not null)
{
count = i + 1;
}
}

return count;
}


internal unsafe void Write(PEImageWriter writer, ref uint position)
{
var numberOfEntries = CalculateNumberOfEntries();
for (int i = 0; i < numberOfEntries; i++)
for (int i = 0; i < Count; i++)
{
ImageDataDirectory rawDataDirectory = default;
var entry = _entries[i];
Expand All @@ -176,13 +201,7 @@ internal unsafe void Write(PEImageWriter writer, ref uint position)
}
}

position += (uint)(numberOfEntries * sizeof(ImageDataDirectory));
}

[InlineArray(15)]
private struct InternalArray
{
private PEObjectBase? _element;
position += (uint)(Count * sizeof(ImageDataDirectory));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.

Expand Down Expand Up @@ -71,9 +71,9 @@ public override unsafe void Read(PEImageReader reader)
{
Revision = header.Revision,
Type = header.Type,
Data = reader.ReadAsStream(header.Length - 8)
};

certificate.Data = reader.ReadAsStream(header.Length - 8);
Certificates.Add(certificate);

if (reader.HasErrors)
Expand Down
17 changes: 17 additions & 0 deletions src/LibObjectFile/PE/DataDirectory/PEUnknownDirectory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.

namespace LibObjectFile.PE;

/// <summary>
/// Represents an unknown directory (when going beyond the known directories).
/// </summary>
public sealed class PEUnknownDirectory : PEDataDirectory
{
internal PEUnknownDirectory(int index) : base((PEDataDirectoryKind)index)
{
}

protected override uint ComputeHeaderSize(PELayoutContext context) => 0;
}
Loading

0 comments on commit 819c74c

Please sign in to comment.