Skip to content

Commit f12216e

Browse files
committed
Add unit test for creating a PE file from scratch
1 parent 64006dd commit f12216e

23 files changed

+684
-63
lines changed

src/LibObjectFile.Tests/LibObjectFile.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<None Remove="PE\NativeConsole2Win64.exe" />
1818
<None Remove="PE\NativeConsoleWin64.exe" />
1919
<None Remove="PE\NativeLibraryWin64.dll" />
20+
<None Remove="PE\RawNativeConsoleWin64.exe" />
2021
<None Remove="small.cpp" />
2122
</ItemGroup>
2223

@@ -42,6 +43,9 @@
4243
<Content Include="PE\NativeLibraryWin64.dll">
4344
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4445
</Content>
46+
<Content Include="PE\RawNativeConsoleWin64.exe">
47+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
48+
</Content>
4549
<Content Include="small.cpp">
4650
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4751
</Content>

src/LibObjectFile.Tests/PE/PEReaderTests.cs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ public partial class PEReaderTests
2121
[DataRow("NativeConsoleWin64.exe")]
2222
[DataRow("NativeConsole2Win64.exe")]
2323
[DataRow("NativeLibraryWin64.dll")]
24+
[DataRow("RawNativeConsoleWin64.exe")]
2425

2526
public async Task TestPrinter(string name)
2627
{
27-
2828
var sourceFile = Path.Combine(AppContext.BaseDirectory, "PE", name);
2929
await using var stream = File.OpenRead(sourceFile);
3030
var peImage = PEFile.Read(stream, new() { EnableStackTrace = true });
@@ -69,7 +69,71 @@ public async Task TestPrinter(string name)
6969
// Compare the input and output buffer
7070
ByteArrayAssert.AreEqual(inputBuffer, outputBuffer, $"Invalid roundtrip for `{name}`");
7171
}
72-
72+
73+
[TestMethod]
74+
public void TestCreatePE()
75+
{
76+
var pe = new PEFile();
77+
78+
// Add a sections
79+
var codeSection = pe.AddSection(PESectionName.Text, 0x1000);
80+
var streamCode = new PEStreamSectionData();
81+
82+
streamCode.Stream.Write([
83+
// SUB RSP, 0x28
84+
0x48, 0x83, 0xEC, 0x28,
85+
// MOV ECX, 0x9C
86+
0xB9, 0x9C, 0x00, 0x00, 0x00,
87+
// CALL ExitProcess (CALL [RIP + 0xFF1])
88+
0xFF, 0x15, 0xF1, 0x0F, 0x00, 0x00,
89+
// INT3
90+
0xCC
91+
]);
92+
93+
codeSection.Content.Add(streamCode);
94+
95+
var dataSection = pe.AddSection(PESectionName.RData, 0x2000);
96+
97+
var streamData = new PEStreamSectionData();
98+
var kernelName = streamData.WriteAsciiString("KERNEL32.DLL");
99+
var exitProcessFunction = streamData.WriteHintName(new(0x178, "ExitProcess"));
100+
101+
var peImportAddressTable = new PEImportAddressTable()
102+
{
103+
exitProcessFunction
104+
};
105+
var iatDirectory = new PEImportAddressTableDirectory()
106+
{
107+
peImportAddressTable
108+
};
109+
dataSection.Content.Add(iatDirectory);
110+
111+
var peImportLookupTable = new PEImportLookupTable()
112+
{
113+
exitProcessFunction
114+
};
115+
dataSection.Content.Add(peImportLookupTable);
116+
117+
var importDirectory = new PEImportDirectory();
118+
importDirectory.Entries.Add(new PEImportDirectoryEntry(kernelName, peImportAddressTable, peImportLookupTable));
119+
dataSection.Content.Add(importDirectory);
120+
121+
dataSection.Content.Add(streamData);
122+
123+
pe.Directories[PEDataDirectoryKind.Import] = importDirectory;
124+
pe.Directories[PEDataDirectoryKind.ImportAddressTable] = iatDirectory;
125+
126+
pe.OptionalHeader.AddressOfEntryPoint = 0x1000;
127+
pe.OptionalHeader.BaseOfCode = 0x1000;
128+
129+
var output = new MemoryStream();
130+
pe.Write(output);
131+
output.Position = 0;
132+
133+
var sourceFile = Path.Combine(AppContext.BaseDirectory, "PE", "generated_win64.exe");
134+
File.WriteAllBytes(sourceFile, output.ToArray());
135+
}
136+
73137
[DataTestMethod]
74138
[DynamicData(nameof(GetWindowsExeAndDlls), DynamicDataSourceType.Method)]
75139
public async Task TestWindows(string sourceFile)
2.5 KB
Binary file not shown.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
DOS Header
2+
Magic = DOS
3+
ByteCountOnLastPage = 0x90
4+
PageCount = 0x3
5+
RelocationCount = 0x0
6+
SizeOfParagraphsHeader = 0x4
7+
MinExtraParagraphs = 0x0
8+
MaxExtraParagraphs = 0xFFFF
9+
InitialSSValue = 0x0
10+
InitialSPValue = 0xB8
11+
Checksum = 0x0
12+
InitialIPValue = 0x0
13+
InitialCSValue = 0x0
14+
FileAddressRelocationTable = 0x40
15+
OverlayNumber = 0x0
16+
Reserved = 0x0, 0x0, 0x0, 0x0
17+
OEMIdentifier = 0x0
18+
OEMInformation = 0x0
19+
Reserved2 = 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
20+
FileAddressPEHeader = 0xC8
21+
22+
DOS Stub
23+
DosStub = 64 bytes
24+
25+
COFF Header
26+
Machine = Amd64
27+
NumberOfSections = 3
28+
TimeDateStamp = 1727726362
29+
PointerToSymbolTable = 0x0
30+
NumberOfSymbols = 0
31+
SizeOfOptionalHeader = 240
32+
Characteristics = ExecutableImage, LargeAddressAware
33+
34+
Optional Header
35+
Magic = PE32Plus
36+
MajorLinkerVersion = 14
37+
MinorLinkerVersion = 41
38+
SizeOfCode = 0x200
39+
SizeOfInitializedData = 0x400
40+
SizeOfUninitializedData = 0x0
41+
AddressOfEntryPoint = 0x1000
42+
BaseOfCode = 0x1000
43+
BaseOfData = 0x0
44+
ImageBase = 0x140000000
45+
SectionAlignment = 0x1000
46+
FileAlignment = 0x200
47+
MajorOperatingSystemVersion = 6
48+
MinorOperatingSystemVersion = 0
49+
MajorImageVersion = 0
50+
MinorImageVersion = 0
51+
MajorSubsystemVersion = 6
52+
MinorSubsystemVersion = 0
53+
Win32VersionValue = 0x0
54+
SizeOfImage = 0x4000
55+
SizeOfHeaders = 0x400
56+
CheckSum = 0x0
57+
Subsystem = WindowsCui
58+
DllCharacteristics = HighEntropyVirtualAddressSpace, DynamicBase, TerminalServerAware
59+
SizeOfStackReserve = 0x100000
60+
SizeOfStackCommit = 0x1000
61+
SizeOfHeapReserve = 0x100000
62+
SizeOfHeapCommit = 0x1000
63+
LoaderFlags = 0x0
64+
NumberOfRvaAndSizes = 0x10
65+
66+
Data Directories
67+
[00] = null
68+
[01] = PEImportDirectory Position = 0x00000744, Size = 0x00000028, RVA = 0x00002144, VirtualSize = 0x00000028
69+
[02] = null
70+
[03] = PEExceptionDirectory Position = 0x00000800, Size = 0x0000000C, RVA = 0x00003000, VirtualSize = 0x0000000C
71+
[04] = null
72+
[05] = null
73+
[06] = PEDebugDirectory Position = 0x00000610, Size = 0x00000038, RVA = 0x00002010, VirtualSize = 0x00000038
74+
[07] = null
75+
[08] = null
76+
[09] = null
77+
[10] = null
78+
[11] = null
79+
[12] = PEImportAddressTableDirectory Position = 0x00000600, Size = 0x00000010, RVA = 0x00002000, VirtualSize = 0x00000010
80+
[13] = null
81+
[14] = null
82+
[15] = null
83+
84+
Section Headers
85+
[00] .text PESection Position = 0x00000400, Size = 0x00000200, RVA = 0x00001000, VirtualSize = 0x00000010, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)
86+
[01] .rdata PESection Position = 0x00000600, Size = 0x00000200, RVA = 0x00002000, VirtualSize = 0x0000019C, Characteristics = 0x40000040 (ContainsInitializedData, MemRead)
87+
[02] .pdata PESection Position = 0x00000800, Size = 0x00000200, RVA = 0x00003000, VirtualSize = 0x0000000C, Characteristics = 0x40000040 (ContainsInitializedData, MemRead)
88+
89+
Sections
90+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
91+
[00] .text PESection Position = 0x00000400, Size = 0x00000200, RVA = 0x00001000, VirtualSize = 0x00000010, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)
92+
93+
[00] PEStreamSectionData Position = 0x00000400, Size = 0x00000010, RVA = 0x00001000, VirtualSize = 0x00000010
94+
95+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
96+
[01] .rdata PESection Position = 0x00000600, Size = 0x00000200, RVA = 0x00002000, VirtualSize = 0x0000019C, Characteristics = 0x40000040 (ContainsInitializedData, MemRead)
97+
98+
[00] PEImportAddressTableDirectory Position = 0x00000600, Size = 0x00000010, RVA = 0x00002000, VirtualSize = 0x00000010
99+
[00] PEImportAddressTable Position = 0x00000600, Size = 0x00000010, RVA = 0x00002000, VirtualSize = 0x00000010
100+
[0] PEImportHintName { Hint = 376, Name = ExitProcess } (RVA = 0x2180, PEStreamSectionData { RVA = 0x2180, VirtualSize = 0x1C, Position = 0x780, Size = 0x1C }, Offset = 0x0)
101+
102+
103+
[01] PEDebugDirectory Position = 0x00000610, Size = 0x00000038, RVA = 0x00002010, VirtualSize = 0x00000038
104+
[0] Type = POGO, Characteristics = 0x0, Version = 0.0, TimeStamp = 0x66FB031A, Data = RVA = 0x00002060 (PEDebugStreamSectionData[3] -> .rdata)
105+
[1] Type = ILTCG, Characteristics = 0x0, Version = 0.0, TimeStamp = 0x66FB031A, Data = null
106+
107+
[02] PEStreamSectionData Position = 0x00000648, Size = 0x00000018, RVA = 0x00002048, VirtualSize = 0x00000018
108+
109+
[03] PEDebugStreamSectionData Position = 0x00000660, Size = 0x000000DC, RVA = 0x00002060, VirtualSize = 0x000000DC
110+
111+
[04] PEStreamSectionData Position = 0x0000073C, Size = 0x00000008, RVA = 0x0000213C, VirtualSize = 0x00000008
112+
113+
[05] PEImportDirectory Position = 0x00000744, Size = 0x00000028, RVA = 0x00002144, VirtualSize = 0x00000028
114+
[0] ImportDllNameLink = KERNEL32.dll (RVA = 0x218E, PEStreamSectionData { RVA = 0x2180, VirtualSize = 0x1C, Position = 0x780, Size = 0x1C }, Offset = 0xE)
115+
[0] ImportAddressTable = RVA = 0x00002000 (PEImportAddressTable[0] -> PEImportAddressTableDirectory[0] -> .rdata)
116+
[0] ImportLookupTable = RVA = 0x00002170 (PEImportLookupTable[7] -> .rdata)
117+
118+
119+
[06] PEStreamSectionData Position = 0x0000076C, Size = 0x00000004, RVA = 0x0000216C, VirtualSize = 0x00000004
120+
121+
[07] PEImportLookupTable Position = 0x00000770, Size = 0x00000010, RVA = 0x00002170, VirtualSize = 0x00000010
122+
[0] PEImportHintName { Hint = 376, Name = ExitProcess } (RVA = 0x2180, PEStreamSectionData { RVA = 0x2180, VirtualSize = 0x1C, Position = 0x780, Size = 0x1C }, Offset = 0x0)
123+
124+
[08] PEStreamSectionData Position = 0x00000780, Size = 0x0000001C, RVA = 0x00002180, VirtualSize = 0x0000001C
125+
126+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
127+
[02] .pdata PESection Position = 0x00000800, Size = 0x00000200, RVA = 0x00003000, VirtualSize = 0x0000000C, Characteristics = 0x40000040 (ContainsInitializedData, MemRead)
128+
129+
[00] PEExceptionDirectory Position = 0x00000800, Size = 0x0000000C, RVA = 0x00003000, VirtualSize = 0x0000000C
130+
[0] Begin = RVA = 0x1000, PEStreamSectionData { RVA = 0x1000, VirtualSize = 0x10, Position = 0x400, Size = 0x10 }, Offset = 0x0
131+
[0] End = RVA = 0x1010, PEStreamSectionData { RVA = 0x1000, VirtualSize = 0x10, Position = 0x400, Size = 0x10 }, Offset = 0x10
132+
[0] UnwindInfoAddress = RVA = 0x213C, PEStreamSectionData { RVA = 0x213C, VirtualSize = 0x8, Position = 0x73C, Size = 0x8 }, Offset = 0x0
133+
134+

src/LibObjectFile/PE/DataDirectory/PECompositeSectionData.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
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

5+
using System.Collections;
56
using System.Collections.Generic;
67
using System.Diagnostics;
78
using System.Diagnostics.CodeAnalysis;
@@ -15,7 +16,7 @@ namespace LibObjectFile.PE;
1516
/// <summary>
1617
/// A section data that contains a list of <see cref="PESectionData"/> and an optional header of data.
1718
/// </summary>
18-
public abstract class PECompositeSectionData : PESectionData
19+
public abstract class PECompositeSectionData : PESectionData, IEnumerable<PESectionData>
1920
{
2021
protected PECompositeSectionData()
2122
{
@@ -133,4 +134,12 @@ protected sealed override void UpdateRVAInChildren()
133134
va += (uint)table.Size;
134135
}
135136
}
137+
138+
public void Add(PESectionData data) => Content.Add(data);
139+
140+
public List<PESectionData>.Enumerator GetEnumerator() => Content.GetEnumerator();
141+
142+
IEnumerator<PESectionData> IEnumerable<PESectionData>.GetEnumerator() => Content.GetEnumerator();
143+
144+
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Content).GetEnumerator();
136145
}

0 commit comments

Comments
 (0)