@@ -249,7 +249,7 @@ The main entry-point for reading/writing PE file is the [`PEFile`](https://githu
249
249
250
250
![ PE class diagram] ( PE.png )
251
251
252
- ## Sections and Directories
252
+ #### Sections and Directories
253
253
254
254
In ` LibObjectFile ` all the section data ` PESectionData ` - e.g code, data but also including PE directories - are part of either:
255
255
@@ -263,7 +263,7 @@ A PE Directory itself can contain also a collection of `PESectionData`.
263
263
264
264
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.
265
265
266
- ## VA, RVA, RVO
266
+ #### VA, RVA, RVO
267
267
268
268
In the PE file format, there are different types of addresses:
269
269
@@ -275,6 +275,269 @@ In the PE file format, there are different types of addresses:
275
275
276
276
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.
277
277
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
+
278
541
### Links
279
542
280
543
- [ PE and COFF Specification] ( https://docs.microsoft.com/en-us/windows/win32/debug/pe-format )
0 commit comments