Skip to content

Commit e6786d6

Browse files
committed
Add some advanced documentation
1 parent d44d387 commit e6786d6

File tree

1 file changed

+265
-2
lines changed

1 file changed

+265
-2
lines changed

doc/readme.md

+265-2
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ The main entry-point for reading/writing PE file is the [`PEFile`](https://githu
249249

250250
![PE class diagram](PE.png)
251251

252-
## Sections and Directories
252+
#### Sections and Directories
253253

254254
In `LibObjectFile` all the section data `PESectionData` - e.g code, data but also including PE directories - are part of either:
255255

@@ -263,7 +263,7 @@ A PE Directory itself can contain also a collection of `PESectionData`.
263263

264264
If the size of a section data is modified (e.g adding elements to a directory table or modifying a stream in a `PEStreamSectionData`), it is important to call `PEFile.UpdateLayout` to update the layout of the PE file.
265265

266-
## VA, RVA, RVO
266+
#### VA, RVA, RVO
267267

268268
In the PE file format, there are different types of addresses:
269269

@@ -275,6 +275,269 @@ In the PE file format, there are different types of addresses:
275275

276276
In `LibObjectFile` links to RVA between section and section datas are done through a `IPELink` that is combining a reference to a `PEObjectBase` and a `RVO`. It means that RVA are always up to date and linked to the right section data.
277277

278+
### Reading a PE File
279+
280+
The PE API allows to read from a `System.IO.Stream` via the method `PEFile.Read`:
281+
282+
```csharp
283+
PEFile pe = PEFile.Read(inputStream);
284+
foreach(var section in pe.Sections)
285+
{
286+
Console.WriteLine($"{section}");
287+
}
288+
```
289+
290+
### Writing a PE File
291+
292+
The PE API allows to write to a `System.IO.Stream` via the method `PEFile.Write`:
293+
294+
```csharp
295+
PEFile pe = PEFile.Read(inputStream);
296+
// Modify the PE file
297+
// ....
298+
pe.Write(stream);
299+
```
300+
301+
### Printing a PE File
302+
303+
You can print a PE file to a textual by using the extension method `PEFile.Print(TextWriter)`:
304+
305+
```csharp
306+
PEFile pe = PEFile.Read(inputStream);
307+
pe.Print(Console.Out);
308+
```
309+
310+
It will generate an output like this:
311+
312+
```
313+
DOS Header
314+
Magic = DOS
315+
ByteCountOnLastPage = 0x90
316+
PageCount = 0x3
317+
RelocationCount = 0x0
318+
SizeOfParagraphsHeader = 0x4
319+
MinExtraParagraphs = 0x0
320+
MaxExtraParagraphs = 0xFFFF
321+
InitialSSValue = 0x0
322+
InitialSPValue = 0xB8
323+
Checksum = 0x0
324+
InitialIPValue = 0x0
325+
InitialCSValue = 0x0
326+
FileAddressRelocationTable = 0x40
327+
OverlayNumber = 0x0
328+
Reserved = 0x0, 0x0, 0x0, 0x0
329+
OEMIdentifier = 0x0
330+
OEMInformation = 0x0
331+
Reserved2 = 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
332+
FileAddressPEHeader = 0xC8
333+
334+
DOS Stub
335+
DosStub = 64 bytes
336+
337+
COFF Header
338+
Machine = Amd64
339+
NumberOfSections = 3
340+
TimeDateStamp = 1727726362
341+
PointerToSymbolTable = 0x0
342+
NumberOfSymbols = 0
343+
SizeOfOptionalHeader = 240
344+
Characteristics = ExecutableImage, LargeAddressAware
345+
346+
Optional Header
347+
Magic = PE32Plus
348+
MajorLinkerVersion = 14
349+
MinorLinkerVersion = 41
350+
SizeOfCode = 0x200
351+
SizeOfInitializedData = 0x400
352+
SizeOfUninitializedData = 0x0
353+
AddressOfEntryPoint = RVA = 0x1000, PEStreamSectionData { RVA = 0x1000, VirtualSize = 0x10, Position = 0x400, Size = 0x10 }, Offset = 0x0
354+
BaseOfCode = PESection { .text RVA = 0x1000, VirtualSize = 0x10, Position = 0x400, Size = 0x200, Content[1] }
355+
BaseOfData = 0x0x0
356+
ImageBase = 0x140000000
357+
SectionAlignment = 0x1000
358+
FileAlignment = 0x200
359+
MajorOperatingSystemVersion = 6
360+
MinorOperatingSystemVersion = 0
361+
MajorImageVersion = 0
362+
MinorImageVersion = 0
363+
MajorSubsystemVersion = 6
364+
MinorSubsystemVersion = 0
365+
Win32VersionValue = 0x0
366+
SizeOfImage = 0x4000
367+
SizeOfHeaders = 0x400
368+
CheckSum = 0x0
369+
Subsystem = WindowsCui
370+
DllCharacteristics = HighEntropyVirtualAddressSpace, DynamicBase, TerminalServerAware
371+
SizeOfStackReserve = 0x100000
372+
SizeOfStackCommit = 0x1000
373+
SizeOfHeapReserve = 0x100000
374+
SizeOfHeapCommit = 0x1000
375+
LoaderFlags = 0x0
376+
NumberOfRvaAndSizes = 0x10
377+
378+
Data Directories
379+
[00] = null
380+
[01] = PEImportDirectory Position = 0x00000744, Size = 0x00000028, RVA = 0x00002144, VirtualSize = 0x00000028
381+
[02] = null
382+
[03] = PEExceptionDirectory Position = 0x00000800, Size = 0x0000000C, RVA = 0x00003000, VirtualSize = 0x0000000C
383+
[04] = null
384+
[05] = null
385+
[06] = PEDebugDirectory Position = 0x00000610, Size = 0x00000038, RVA = 0x00002010, VirtualSize = 0x00000038
386+
[07] = null
387+
[08] = null
388+
[09] = null
389+
[10] = null
390+
[11] = null
391+
[12] = PEImportAddressTableDirectory Position = 0x00000600, Size = 0x00000010, RVA = 0x00002000, VirtualSize = 0x00000010
392+
[13] = null
393+
[14] = null
394+
[15] = null
395+
396+
Section Headers
397+
[00] .text PESection Position = 0x00000400, Size = 0x00000200, RVA = 0x00001000, VirtualSize = 0x00000010, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)
398+
[01] .rdata PESection Position = 0x00000600, Size = 0x00000200, RVA = 0x00002000, VirtualSize = 0x0000019C, Characteristics = 0x40000040 (ContainsInitializedData, MemRead)
399+
[02] .pdata PESection Position = 0x00000800, Size = 0x00000200, RVA = 0x00003000, VirtualSize = 0x0000000C, Characteristics = 0x40000040 (ContainsInitializedData, MemRead)
400+
401+
Sections
402+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
403+
[00] .text PESection Position = 0x00000400, Size = 0x00000200, RVA = 0x00001000, VirtualSize = 0x00000010, Characteristics = 0x60000020 (ContainsCode, MemExecute, MemRead)
404+
405+
[00] PEStreamSectionData Position = 0x00000400, Size = 0x00000010, RVA = 0x00001000, VirtualSize = 0x00000010
406+
407+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
408+
[01] .rdata PESection Position = 0x00000600, Size = 0x00000200, RVA = 0x00002000, VirtualSize = 0x0000019C, Characteristics = 0x40000040 (ContainsInitializedData, MemRead)
409+
410+
[00] PEImportAddressTableDirectory Position = 0x00000600, Size = 0x00000010, RVA = 0x00002000, VirtualSize = 0x00000010
411+
[00] PEImportAddressTable Position = 0x00000600, Size = 0x00000010, RVA = 0x00002000, VirtualSize = 0x00000010
412+
[0] PEImportHintName { Hint = 376, Name = ExitProcess } (RVA = 0x2180, PEStreamSectionData { RVA = 0x2180, VirtualSize = 0x1C, Position = 0x780, Size = 0x1C }, Offset = 0x0)
413+
414+
415+
[01] PEDebugDirectory Position = 0x00000610, Size = 0x00000038, RVA = 0x00002010, VirtualSize = 0x00000038
416+
[0] Type = POGO, Characteristics = 0x0, Version = 0.0, TimeStamp = 0x66FB031A, Data = RVA = 0x00002060 (PEDebugStreamSectionData[3] -> .rdata)
417+
[1] Type = ILTCG, Characteristics = 0x0, Version = 0.0, TimeStamp = 0x66FB031A, Data = null
418+
419+
[02] PEStreamSectionData Position = 0x00000648, Size = 0x00000018, RVA = 0x00002048, VirtualSize = 0x00000018
420+
421+
[03] PEDebugStreamSectionData Position = 0x00000660, Size = 0x000000DC, RVA = 0x00002060, VirtualSize = 0x000000DC
422+
423+
[04] PEStreamSectionData Position = 0x0000073C, Size = 0x00000008, RVA = 0x0000213C, VirtualSize = 0x00000008
424+
425+
[05] PEImportDirectory Position = 0x00000744, Size = 0x00000028, RVA = 0x00002144, VirtualSize = 0x00000028
426+
[0] ImportDllNameLink = KERNEL32.dll (RVA = 0x218E, PEStreamSectionData { RVA = 0x2180, VirtualSize = 0x1C, Position = 0x780, Size = 0x1C }, Offset = 0xE)
427+
[0] ImportAddressTable = RVA = 0x00002000 (PEImportAddressTable[0] -> PEImportAddressTableDirectory[0] -> .rdata)
428+
[0] ImportLookupTable = RVA = 0x00002170 (PEImportLookupTable[7] -> .rdata)
429+
430+
431+
[06] PEStreamSectionData Position = 0x0000076C, Size = 0x00000004, RVA = 0x0000216C, VirtualSize = 0x00000004
432+
433+
[07] PEImportLookupTable Position = 0x00000770, Size = 0x00000010, RVA = 0x00002170, VirtualSize = 0x00000010
434+
[0] PEImportHintName { Hint = 376, Name = ExitProcess } (RVA = 0x2180, PEStreamSectionData { RVA = 0x2180, VirtualSize = 0x1C, Position = 0x780, Size = 0x1C }, Offset = 0x0)
435+
436+
[08] PEStreamSectionData Position = 0x00000780, Size = 0x0000001C, RVA = 0x00002180, VirtualSize = 0x0000001C
437+
438+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
439+
[02] .pdata PESection Position = 0x00000800, Size = 0x00000200, RVA = 0x00003000, VirtualSize = 0x0000000C, Characteristics = 0x40000040 (ContainsInitializedData, MemRead)
440+
441+
[00] PEExceptionDirectory Position = 0x00000800, Size = 0x0000000C, RVA = 0x00003000, VirtualSize = 0x0000000C
442+
[0] Begin = RVA = 0x1000, PEStreamSectionData { RVA = 0x1000, VirtualSize = 0x10, Position = 0x400, Size = 0x10 }, Offset = 0x0
443+
[0] End = RVA = 0x1010, PEStreamSectionData { RVA = 0x1000, VirtualSize = 0x10, Position = 0x400, Size = 0x10 }, Offset = 0x10
444+
[0] UnwindInfoAddress = RVA = 0x213C, PEStreamSectionData { RVA = 0x213C, VirtualSize = 0x8, Position = 0x73C, Size = 0x8 }, Offset = 0x0
445+
```
446+
447+
### Creating a PE File
448+
449+
The PE format is complex and requires a lot of information to be created.
450+
451+
While LibObjectFile provides a way to create a PE file from scratch, it is not easy to create a working exe/dll file. If you are trying to create a file from scratch, use the `PEFile.Print` on existing exe/dll files to understand the structure and how to create a similar file.
452+
453+
The following example is a complete example that creates a PE file with a code section that calls `ExitProcess` from `KERNEL32.DLL` with the value `156`:
454+
455+
```csharp
456+
var pe = new PEFile();
457+
458+
// ***************************************************************************
459+
// Code section
460+
// ***************************************************************************
461+
var codeSection = pe.AddSection(PESectionName.Text, 0x1000);
462+
var streamCode = new PEStreamSectionData();
463+
464+
streamCode.Stream.Write([
465+
// SUB RSP, 0x28
466+
0x48, 0x83, 0xEC, 0x28,
467+
// MOV ECX, 0x9C
468+
0xB9, 0x9C, 0x00, 0x00, 0x00,
469+
// CALL ExitProcess (CALL [RIP + 0xFF1])
470+
0xFF, 0x15, 0xF1, 0x0F, 0x00, 0x00,
471+
// INT3
472+
0xCC
473+
]);
474+
475+
codeSection.Content.Add(streamCode);
476+
477+
// ***************************************************************************
478+
// Data section
479+
// ***************************************************************************
480+
var dataSection = pe.AddSection(PESectionName.RData, 0x2000);
481+
482+
var streamData = new PEStreamSectionData();
483+
var kernelName = streamData.WriteAsciiString("KERNEL32.DLL");
484+
var exitProcessFunction = streamData.WriteHintName(new(0x178, "ExitProcess"));
485+
486+
// PEImportAddressTableDirectory comes first, it is referenced by the RIP + 0xFF1, first address being ExitProcess
487+
var peImportAddressTable = new PEImportAddressTable()
488+
{
489+
exitProcessFunction
490+
};
491+
var iatDirectory = new PEImportAddressTableDirectory()
492+
{
493+
peImportAddressTable
494+
};
495+
496+
var peImportLookupTable = new PEImportLookupTable()
497+
{
498+
exitProcessFunction
499+
};
500+
501+
var importDirectory = new PEImportDirectory()
502+
{
503+
Entries =
504+
{
505+
new PEImportDirectoryEntry(kernelName, peImportAddressTable, peImportLookupTable)
506+
}
507+
};
508+
509+
// Layout of the data section
510+
dataSection.Content.Add(iatDirectory);
511+
dataSection.Content.Add(peImportLookupTable);
512+
dataSection.Content.Add(importDirectory);
513+
dataSection.Content.Add(streamData);
514+
515+
// ***************************************************************************
516+
// Optional Header
517+
// ***************************************************************************
518+
pe.OptionalHeader.AddressOfEntryPoint = new(streamCode, 0);
519+
pe.OptionalHeader.BaseOfCode = codeSection;
520+
521+
// ***************************************************************************
522+
// Write the PE to a file
523+
// ***************************************************************************
524+
var output = new MemoryStream();
525+
pe.Write(output, new() { EnableStackTrace = true });
526+
output.Position = 0;
527+
528+
var sourceFile = Path.Combine(AppContext.BaseDirectory, "PE", "RawNativeConsoleWin64_Generated.exe");
529+
File.WriteAllBytes(sourceFile, output.ToArray());
530+
531+
// Check the generated exe
532+
var process = Process.Start(sourceFile);
533+
process.WaitForExit();
534+
Assert.AreEqual(156, process.ExitCode);
535+
```
536+
537+
> Notice that the code above doesn't have to setup the `PEFile.Directories` explicitly.
538+
>
539+
> In fact when writing a PE file, the `PEFile.Write` method will automatically populate the directories based on the content of the sections.
540+
278541
### Links
279542

280543
- [PE and COFF Specification](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format)

0 commit comments

Comments
 (0)