Skip to content

Commit 819c74c

Browse files
committed
Improve support for uncommon headers and PE directories layout
1 parent a8831a1 commit 819c74c

18 files changed

+440
-150
lines changed

src/LibObjectFile.Tests/PE/PEReaderTests.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44

55
using System;
66
using System.IO;
7-
using System.Linq;
8-
using System.Runtime.InteropServices;
97
using System.Threading.Tasks;
108
using LibObjectFile.Diagnostics;
119
using LibObjectFile.PE;
1210
using VerifyMSTest;
13-
using VerifyTests;
1411

1512
namespace LibObjectFile.Tests.PE;
1613

@@ -49,4 +46,33 @@ public async Task TestPrinter(string name)
4946
await Verifier.Verify(afterUpdateText).UseParameters(name).DisableRequireUniquePrefix();
5047
}
5148
}
49+
50+
[TestMethod]
51+
public async Task TestTinyExe97Bytes()
52+
{
53+
// http://www.phreedom.org/research/tinype/
54+
// TinyPE: The smallest possible PE file
55+
// 97 bytes
56+
byte[] data =
57+
[
58+
0x4D, 0x5A, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x01, 0x00, 0x6A, 0x2A, 0x58, 0xC3,
59+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x01, 0x0B, 0x01, 0x08, 0x00,
60+
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
61+
0x04, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00,
62+
0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
63+
0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
64+
0x02
65+
];
66+
67+
var stream = new MemoryStream();
68+
stream.Write(data, 0, data.Length);
69+
stream.Position = 0;
70+
71+
var peImage = PEFile.Read(stream);
72+
var writer = new StringWriter();
73+
peImage.Print(writer);
74+
var afterReadText = writer.ToString();
75+
76+
await Verifier.Verify(afterReadText);
77+
}
5278
}

src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsole2Win64.exe.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Data Directories
7979
[12] = PEImportAddressTableDirectory Position = 0x00001E00, Size = 0x00000250, RVA = 0x00003000, VirtualSize = 0x00000250
8080
[13] = PEDelayImportDirectory Position = 0x000029A8, Size = 0x00000040, RVA = 0x00003BA8, VirtualSize = 0x00000040
8181
[14] = null
82+
[15] = null
8283

8384
Section Headers
8485
[00] .text PESection Position = 0x00000400, Size = 0x00001A00, RVA = 0x00001000, VirtualSize = 0x000019E9, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)

src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsoleWin64.exe.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Data Directories
7979
[12] = PEImportAddressTableDirectory Position = 0x00001600, Size = 0x000001F0, RVA = 0x00003000, VirtualSize = 0x000001F0
8080
[13] = null
8181
[14] = null
82+
[15] = null
8283

8384
Section Headers
8485
[00] .text PESection Position = 0x00000400, Size = 0x00001200, RVA = 0x00001000, VirtualSize = 0x000010C9, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)

src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeLibraryWin64.dll.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Data Directories
7979
[12] = PEImportAddressTableDirectory Position = 0x00001400, Size = 0x000000E8, RVA = 0x00002000, VirtualSize = 0x000000E8
8080
[13] = null
8181
[14] = null
82+
[15] = null
8283

8384
Section Headers
8485
[00] .text PESection Position = 0x00000400, Size = 0x00001000, RVA = 0x00001000, VirtualSize = 0x00000F18, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
DOS Header
2+
Magic = DOS
3+
ByteCountOnLastPage = 0x0
4+
PageCount = 0x4550
5+
RelocationCount = 0x0
6+
SizeOfParagraphsHeader = 0x14C
7+
MinExtraParagraphs = 0x1
8+
MaxExtraParagraphs = 0x2A6A
9+
InitialSSValue = 0xC358
10+
InitialSPValue = 0x0
11+
Checksum = 0x0
12+
InitialIPValue = 0x0
13+
InitialCSValue = 0x0
14+
FileAddressRelocationTable = 0x4
15+
OverlayNumber = 0x103
16+
Reserved = 0x10B, 0x8, 0x4, 0x0
17+
OEMIdentifier = 0x0
18+
OEMInformation = 0x0
19+
Reserved2 = 0x4, 0x0, 0xC, 0x0, 0x4, 0x0, 0xC, 0x0, 0x0, 0x40
20+
FileAddressPEHeader = 0x4
21+
22+
DOS Stub
23+
DosStub = 0 bytes
24+
25+
COFF Header
26+
Machine = I386
27+
NumberOfSections = 1
28+
TimeDateStamp = 3277335146
29+
PointerToSymbolTable = 0x0
30+
NumberOfSymbols = 0
31+
SizeOfOptionalHeader = 4
32+
Characteristics = RelocsStripped, ExecutableImage, Bit32Machine
33+
34+
Optional Header
35+
Magic = PE32
36+
MajorLinkerVersion = 8
37+
MinorLinkerVersion = 0
38+
SizeOfCode = 0x4
39+
SizeOfInitializedData = 0x0
40+
SizeOfUninitializedData = 0x4
41+
AddressOfEntryPoint = 0xC
42+
BaseOfCode = 0x4
43+
BaseOfData = 0xC
44+
ImageBase = 0x0
45+
SectionAlignment = 0x4
46+
FileAlignment = 0x4
47+
MajorOperatingSystemVersion = 4
48+
MinorOperatingSystemVersion = 0
49+
MajorImageVersion = 0
50+
MinorImageVersion = 0
51+
MajorSubsystemVersion = 4
52+
MinorSubsystemVersion = 0
53+
Win32VersionValue = 0x0
54+
SizeOfImage = 0x68
55+
SizeOfHeaders = 0x64
56+
CheckSum = 0x0
57+
Subsystem = WindowsGui
58+
DllCharacteristics = 0
59+
SizeOfStackReserve = 0x0
60+
SizeOfStackCommit = 0x0
61+
SizeOfHeapReserve = 0x0
62+
SizeOfHeapCommit = 0x0
63+
LoaderFlags = 0x0
64+
NumberOfRvaAndSizes = 0x0
65+
66+
Section Headers
67+
[00]  PESection Position = 0x0000000C, Size = 0x00000004, RVA = 0x0000000C, VirtualSize = 0x00000004, Characteristics = 0x00000004 (TypeGroup)
68+
69+
Sections
70+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
71+
[00]  PESection Position = 0x0000000C, Size = 0x00000004, RVA = 0x0000000C, VirtualSize = 0x00000004, Characteristics = 0x00000004 (TypeGroup)
72+
73+
[00] PEStreamSectionData Position = 0x0000000C, Size = 0x00000004, RVA = 0x0000000C, VirtualSize = 0x00000004
74+

src/LibObjectFile/Diagnostics/DiagnosticId.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,10 @@ public enum DiagnosticId
130130
PE_ERR_TooManySections = 3012,
131131
PE_ERR_FileAlignmentNotPowerOfTwo = 3013,
132132
PE_ERR_SectionAlignmentNotPowerOfTwo = 3014,
133-
PE_ERR_SectionAlignmentLessThanFileAlignment = 315,
134-
133+
PE_ERR_SectionAlignmentLessThanFileAlignment = 3015,
134+
PE_ERR_InvalidPEHeaderPosition = 3016,
135+
PE_ERR_InvalidNumberOfDataDirectories = 3017,
136+
135137
// PE Exception directory
136138
PE_ERR_InvalidExceptionDirectory_Entries = 3100,
137139
PE_ERR_InvalidExceptionDirectory_Entry = 3101,

src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ internal static PEDataDirectory Create(PEDataDirectoryKind kind, bool is32Bits)
4646
PEDataDirectoryKind.DelayImport => new PEDelayImportDirectory(),
4747
PEDataDirectoryKind.ImportAddressTable => new PEImportAddressTableDirectory(),
4848
PEDataDirectoryKind.ClrMetadata => new PEClrMetadata(),
49-
_ => throw new ArgumentOutOfRangeException(nameof(kind))
49+
_ => new PEUnknownDirectory((int)kind)
5050
};
5151
}
52-
}
52+
}

src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs

Lines changed: 76 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.Generic;
88
using System.ComponentModel;
99
using System.Diagnostics;
10+
using System.Reflection;
1011
using System.Runtime.CompilerServices;
1112

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

2324
internal PEDirectoryTable()
2425
{
26+
_entries = [];
2527
}
2628

27-
public PEObjectBase? this[PEDataDirectoryKind kind] => _entries[(int)kind];
29+
/// <summary>
30+
/// Gets the directory entry at the specified index.
31+
/// </summary>
32+
/// <param name="index">The index of the directory entry to get.</param>
33+
/// <returns>The directory entry at the specified index.</returns>
34+
/// <exception cref="ArgumentOutOfRangeException">Thrown if the index is out of range.</exception>
35+
public PEObjectBase? this[int index]
36+
{
37+
get
38+
{
39+
if (index < 0 || index >= _count)
40+
{
41+
throw new ArgumentOutOfRangeException(nameof(index));
42+
}
43+
return _entries[index];
44+
}
45+
}
2846

2947
/// <summary>
30-
/// Gets the number of directory entries in the array.
48+
/// Gets the directory entry of the specified kind. Must be within the bounds of <see cref="Count"/>.
3149
/// </summary>
32-
public int Count => _count;
33-
50+
/// <param name="kind">The kind of directory entry to get.</param>
51+
/// <returns></returns>
52+
/// <exception cref="ArgumentOutOfRangeException">Thrown if the kind is out of range.</exception>
53+
public PEObjectBase? this[PEDataDirectoryKind kind]
54+
{
55+
get
56+
{
57+
int index = (int)(ushort)kind;
58+
if (index < 0 || index >= _count)
59+
{
60+
throw new ArgumentOutOfRangeException(nameof(kind));
61+
}
62+
return _entries[(int)kind];
63+
}
64+
}
65+
66+
/// <summary>
67+
/// Gets the maximum number of directory entries in the array.
68+
/// </summary>
69+
public int Count
70+
{
71+
get => _count;
72+
73+
set
74+
{
75+
ArgumentOutOfRangeException.ThrowIfLessThan(value, 0);
76+
77+
var previousCount = _count;
78+
// If the count is reduced, we need to check that all entries are null after
79+
for (int i = value; i < previousCount; i++)
80+
{
81+
if (_entries[i] is not null)
82+
{
83+
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}");
84+
}
85+
}
86+
87+
if (_entries.Length < value)
88+
{
89+
Array.Resize(ref _entries, value);
90+
}
91+
92+
_count = value;
93+
}
94+
}
95+
3496
/// <summary>
3597
/// Gets the export directory information from the PE file.
3698
/// </summary>
@@ -112,60 +174,23 @@ internal PEDirectoryTable()
112174
[EditorBrowsable(EditorBrowsableState.Never)]
113175
public Enumerator GetEnumerator() => new(this);
114176

115-
internal void Set(PESecurityCertificateDirectory? directory)
116-
{
117-
var kind = PEDataDirectoryKind.SecurityCertificate;
118-
ref var entry = ref _entries[(int)kind];
119-
var previousEntry = entry;
120-
entry = directory;
177+
internal void Set(PESecurityCertificateDirectory? directory) => Set(PEDataDirectoryKind.SecurityCertificate, directory);
121178

122-
if (previousEntry is not null)
123-
{
124-
_count--;
125-
}
179+
internal void Set(PEDataDirectoryKind kind, PEObjectBase? directory) => Set((int)kind, directory);
126180

127-
if (directory is not null)
128-
{
129-
_count++;
130-
}
131-
}
132-
133-
internal void Set(PEDataDirectoryKind kind, PEDataDirectory? directory)
181+
internal void Set(int index, PEObjectBase? directory)
134182
{
135-
ref var entry = ref _entries[(int)kind];
136-
var previousEntry = entry;
137-
entry = directory;
138-
139-
if (previousEntry is not null)
183+
if (index >= Count)
140184
{
141-
_count--;
185+
throw new ArgumentOutOfRangeException(nameof(index), $"The directory entry only accepts {Count} entries. Set the count explicitly to allow more entries.");
142186
}
143187

144-
if (directory is not null)
145-
{
146-
_count++;
147-
}
188+
_entries[index] = directory;
148189
}
149-
150-
internal int CalculateNumberOfEntries()
151-
{
152-
int count = 0;
153-
ReadOnlySpan<PEObjectBase?> span = _entries;
154-
for(int i = 0; i < span.Length; i++)
155-
{
156-
if (_entries[i] is not null)
157-
{
158-
count = i + 1;
159-
}
160-
}
161-
162-
return count;
163-
}
164-
190+
165191
internal unsafe void Write(PEImageWriter writer, ref uint position)
166192
{
167-
var numberOfEntries = CalculateNumberOfEntries();
168-
for (int i = 0; i < numberOfEntries; i++)
193+
for (int i = 0; i < Count; i++)
169194
{
170195
ImageDataDirectory rawDataDirectory = default;
171196
var entry = _entries[i];
@@ -176,13 +201,7 @@ internal unsafe void Write(PEImageWriter writer, ref uint position)
176201
}
177202
}
178203

179-
position += (uint)(numberOfEntries * sizeof(ImageDataDirectory));
180-
}
181-
182-
[InlineArray(15)]
183-
private struct InternalArray
184-
{
185-
private PEObjectBase? _element;
204+
position += (uint)(Count * sizeof(ImageDataDirectory));
186205
}
187206

188207
/// <summary>

src/LibObjectFile/PE/DataDirectory/PESecurityCertificateDirectory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Alexandre Mutel. All rights reserved.
1+
// Copyright (c) Alexandre Mutel. All rights reserved.
22
// This file is licensed under the BSD-Clause 2 license.
33
// See the license.txt file in the project root for more information.
44

@@ -71,9 +71,9 @@ public override unsafe void Read(PEImageReader reader)
7171
{
7272
Revision = header.Revision,
7373
Type = header.Type,
74+
Data = reader.ReadAsStream(header.Length - 8)
7475
};
7576

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

7979
if (reader.HasErrors)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Alexandre Mutel. All rights reserved.
2+
// This file is licensed under the BSD-Clause 2 license.
3+
// See the license.txt file in the project root for more information.
4+
5+
namespace LibObjectFile.PE;
6+
7+
/// <summary>
8+
/// Represents an unknown directory (when going beyond the known directories).
9+
/// </summary>
10+
public sealed class PEUnknownDirectory : PEDataDirectory
11+
{
12+
internal PEUnknownDirectory(int index) : base((PEDataDirectoryKind)index)
13+
{
14+
}
15+
16+
protected override uint ComputeHeaderSize(PELayoutContext context) => 0;
17+
}

0 commit comments

Comments
 (0)