@@ -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
254254In ` 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
264264If 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
268268In 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
276276In ` 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 , 0x 1000 );
462+ var streamCode = new PEStreamSectionData ();
463+
464+ streamCode .Stream .Write ([
465+ // SUB RSP, 0x28
466+ 0x 48 , 0x 83 , 0x EC , 0x 28 ,
467+ // MOV ECX, 0x9C
468+ 0x B9 , 0x 9C , 0x 00 , 0x 00 , 0x 00 ,
469+ // CALL ExitProcess (CALL [RIP + 0xFF1])
470+ 0x FF , 0x 15 , 0x F1 , 0x 0F , 0x 00 , 0x 00 ,
471+ // INT3
472+ 0x CC
473+ ]);
474+
475+ codeSection .Content .Add (streamCode );
476+
477+ // ***************************************************************************
478+ // Data section
479+ // ***************************************************************************
480+ var dataSection = pe .AddSection (PESectionName .RData , 0x 2000 );
481+
482+ var streamData = new PEStreamSectionData ();
483+ var kernelName = streamData .WriteAsciiString (" KERNEL32.DLL" );
484+ var exitProcessFunction = streamData .WriteHintName (new (0x 178 , " 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