-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFileFormat.cs
1157 lines (1045 loc) · 39.2 KB
/
FileFormat.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// CodeNode is written though reflection
#pragma warning disable 0649 // CS0649: Field '...' is never assigned to
// II.25 File format extensions to PE
[Ecma("II.25.1")]
sealed class FileFormat : CodeNode
{
public PEHeader PEHeader;
public Section[] Sections;
protected override void InnerRead() {
AddChild(nameof(PEHeader));
Sections = PEHeader.SectionHeaders.Select(h => new Section(h)).ToArray();
AddChildren(nameof(Sections));
End = Sections.Max(s => s.End);
}
}
[Ecma("II.25.2")]
sealed class PEHeader : CodeNode
{
[OrderedField] public DosHeader DosHeader;
[OrderedField] public PESignature PESignature;
[OrderedField] public PEFileHeader PEFileHeader;
[OrderedField] public PEOptionalHeader PEOptionalHeader;
[OrderedField] public SectionHeader[] SectionHeaders;
protected override int GetCount(string field) => field switch {
nameof(SectionHeaders) => PEFileHeader.NumberOfSections,
_ => base.GetCount(field),
};
}
[Ecma("II.25.2.1")]
sealed class DosHeader : CodeNode
{
[Expected('M')]
public char MagicM;
[Expected('Z')]
public char MagicZ;
[Expected(0x90)]
public ushort BytesOnLastPageOfFile;
[Expected(3)]
public ushort PagesInFile;
[Expected(0)]
public ushort Relocations;
[Expected(4)]
public ushort SizeOfHeaderInParagraphs;
[Expected(0)]
public ushort MinimumExtraParagraphsNeeded;
[Expected(0xFFFF)]
public ushort MaximumExtraParagraphsNeeded;
[Expected(0)]
public ushort InitialRelativeStackSegmentValue;
[Expected(0xB8)]
public ushort InitialSP;
[Expected(0)]
public ushort Checksum;
[Expected(0)]
public ushort InitialIP;
[Expected(0)]
public ushort InitialRelativeCS;
[Expected(0x40)]
[Description("Pointer to the Relocation Table, which is size 0.")]
public ushort RawAddressOfRelocation;
[Expected(0)]
public ushort OverlayNumber;
[Expected(0)]
public ulong Reserved;
[Expected(0)]
public ushort OemIdentifier;
[Expected(0)]
public ushort OemInformation;
[Expected(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00})]
public byte[] Reserved2;
[Expected(0x80)]
public LfaNewNode LfaNew;
[Expected(new byte[] { 0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD,
0x21, 0xb8, 0x01, 0x4C, 0xCD, 0x21 })]
public byte[] DosCode;
[Expected("This program cannot be run in DOS mode.\r\r\n$")]
public char[] Message;
[Expected(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })]
public byte[] Reserved3;
public sealed class LfaNewNode : CodeNode
{
public uint val;
protected override void InnerRead() {
base.InnerRead();
NodeValue = Children.Single().NodeValue;
Children.Clear();
}
public override CodeNode Link => Bytes.FileFormat.PEHeader.PESignature;
}
}
[Ecma("II.25.2.1")]
class PESignature : CodeNode
{
[Expected('P')]
public char MagicP;
[Expected('E')]
public char MagicE;
[Expected(0)]
public ushort Reserved;
}
[Ecma("II.25.2.2")]
class PEFileHeader : CodeNode
{
[OrderedField]
public MachineType Machine;
[Description("Number of sections; indicates size of the Section Table, which immediately follows the headers.")]
public ushort NumberOfSections;
[Description("Time and date the file was created in seconds since January 1st 1970 00:00:00 or 0.")]
public uint TimeDateStamp;
[Description("Always 0.")]
[Expected(0)]
public uint PointerToSymbolTable;
[Description("Always 0.")]
[Expected(0)]
public uint NumberOfSymbols;
[Description("Size of the optional header, the format is described below.")]
public ushort OptionalHeaderSize; //TODO(size)
[Description("Flags indicating attributes of the file.")]
[Ecma("II.25.2.2.1")]
public ushort Characteristics;
}
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
[Ecma("II.25.2.2")] // MAYBE it would be more useful to link to the PE format docs instead, but that's not possible in the protocol yet
enum MachineType : ushort
{
UNKNOWN = 0x0,
[Description("Alpha AXP, 32-bit address space")]
ALPHA = 0x184,
[Description("Alpha 64, 64-bit address space")]
ALPHA64 = 0x284,
[Description("Matsushita AM33")]
AM33 = 0x1d3,
[Description("x64")]
AMD64 = 0x8664,
[Description("ARM little endian")]
ARM = 0x1c0,
[Description("ARM64 little endian")]
ARM64 = 0xaa64,
[Description("ARM Thumb-2 little endian")]
ARMNT = 0x1c4,
[Description("AXP 64 (Same as Alpha 64)")]
AXP64 = 0x284,
[Description("EFI byte code")]
EBC = 0xebc,
[Description("Intel 386 or later processors and compatible processors")]
I386 = 0x14c,
[Description("Intel Itanium processor family")]
IA64 = 0x200,
[Description("LoongArch 32-bit processor family")]
LOONGARCH32 = 0x6232,
[Description("LoongArch 64-bit processor family")]
LOONGARCH64 = 0x6264,
[Description("Mitsubishi M32R little endian")]
M32R = 0x9041,
[Description("MIPS16")]
MIPS16 = 0x266,
[Description("MIPS with FPU")]
MIPSFPU = 0x366,
[Description("MIPS16 with FPU")]
MIPSFPU16 = 0x466,
[Description("Power PC little endian")]
POWERPC = 0x1f0,
[Description("Power PC with floating point support")]
POWERPCFP = 0x1f1,
[Description("MIPS little endian")]
R4000 = 0x166,
[Description("RISC-V 32-bit address space")]
RISCV32 = 0x5032,
[Description("RISC-V 64-bit address space")]
RISCV64 = 0x5064,
[Description("RISC-V 128-bit address space")]
RISCV128 = 0x5128,
[Description("Hitachi SH3")]
SH3 = 0x1a2,
[Description("Hitachi SH3 DSP")]
SH3DSP = 0x1a3,
[Description("Hitachi SH4")]
SH4 = 0x1a6,
[Description("Hitachi SH5")]
SH5 = 0x1a8,
[Description("Thumb")]
THUMB = 0x1c2,
[Description("MIPS little-endian WCE v2")]
WCEMIPSV2 = 0x169,
}
[Ecma("II.25.2.3")]
sealed class PEOptionalHeader : CodeNode
{
public PEHeaderStandardFields PEHeaderStandardFields;
[Description("RVA of the data section. (This is a hint to the loader.) Only present in PE32, not PE32+")]
public int BaseOfData = -1;
public PEHeaderWindowsNtSpecificFields<uint> PEHeaderWindowsNtSpecificFields32;
public PEHeaderWindowsNtSpecificFields<ulong> PEHeaderWindowsNtSpecificFields64;
public PEHeaderHeaderDataDirectories PEHeaderHeaderDataDirectories;
protected override void InnerRead() {
AddChild(nameof(PEHeaderStandardFields));
switch (PEHeaderStandardFields.Magic) {
case PE32Magic.PE32:
AddChild(nameof(BaseOfData));
AddChild(nameof(PEHeaderWindowsNtSpecificFields32));
break;
case PE32Magic.PE32plus:
AddChild(nameof(PEHeaderWindowsNtSpecificFields64));
break;
default:
throw new InvalidOperationException($"Magic not recognized: {PEHeaderStandardFields.Magic:X}");
}
Children.Last().NodeName = "PEHeaderWindowsNtSpecificFields";
AddChild(nameof(PEHeaderHeaderDataDirectories));
}
}
[Ecma("II.25.2.3.1")]
sealed class PEHeaderStandardFields : CodeNode
{
[Description("Identifies version.")]
public PE32Magic Magic;
[Description("Spec says always 6, sometimes more.")]
public byte LMajor;
[Description("Always 0.")]
[Expected(0)]
public byte LMinor;
[Description("Size of the code (text) section, or the sum of all code sections if there are multiple sections.")]
public uint CodeSize; //TODO(size)
[Description("Size of the initialized data section, or the sum of all such sections if there are multiple data sections.")]
public uint InitializedDataSize; //TODO(size)
[Description("Size of the uninitialized data section, or the sum of all such sections if there are multiple unitinitalized data sections.")]
public uint UninitializedDataSize; //TODO(size)
[Description("RVA of entry point , needs to point to bytes 0xFF 0x25 followed by the RVA in a section marked execute/read for EXEs or 0 for DLLs")]
public uint EntryPointRVA;
[Description("RVA of the code section. (This is a hint to the loader.)")]
public uint BaseOfCode; //TODO(link) -- TODO: assert that any field with RVA in Name or Description should have .Link?
}
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only
[Ecma("II.25.2.3.1")]
enum PE32Magic : ushort
{
PE32 = 0x10b,
PE32plus = 0x20b,
}
[Ecma("II.25.2.3.2")]
sealed class PEHeaderWindowsNtSpecificFields<Tint> : CodeNode
{
[Description("Shall be a multiple of 0x10000.")]
public Tint ImageBase;
[Description("Shall be greater than File Alignment.")]
public uint SectionAlignment;
[Description("Should be 0x200.")]
[Expected(0x200)]
public uint FileAlignment;
[Description("Should be 5.")]
public ushort OSMajor;
[Description("Should be 0.")]
[Expected(0)]
public ushort OSMinor;
[Description("Should be 0.")]
[Expected(0)]
public ushort UserMajor;
[Description("Should be 0.")]
[Expected(0)]
public ushort UserMinor;
[Description("Should be 5.")]
//[Expected(5)]
public ushort SubSysMajor;
[Description("Should be 0.")]
[Expected(0)]
public ushort SubSysMinor;
[Description("Shall be zero")]
[Expected(0)]
public uint Reserved;
[Description("Size, in bytes, of image, including all headers and padding; shall be a multiple of Section Alignment.")]
public uint ImageSize; //TODO(size)
[Description("Combined size of MS-DOS Header, PE Header, PE Optional Header and padding; shall be a multiple of the file alignment.")]
public uint HeaderSize; //TODO(size)
[Description("Should be 0.")]
[Expected(0)]
public uint FileChecksum;
[Description("Subsystem required to run this image. Shall be either IMAGE_SUBSYSTEM_WINDOWS_CUI (0x3) or IMAGE_SUBSYSTEM_WINDOWS_GUI (0x2).")]
public ushort SubSystem;
[Description("Bits 0x100f shall be zero.")]
public DllCharacteristics DLLFlags;
[Description("Often 1Mb for x86 or 4Mb for x64.")]
public Tint StackReserveSize;
[Description("Often 4Kb for x86 or 16Kb for x64.")]
public Tint StackCommitSize;
[Description("Should be 0x100000 (1Mb).")]
[Expected(0x100000)]
public Tint HeapReserveSize;
[Description("Often 4Kb for x86 or 8Kb for x64.")]
public Tint HeapCommitSize;
[Description("Shall be 0")]
[Expected(0)]
public uint LoaderFlags;
[Description("Shall be 0x10")]
[Expected(0x10)]
public uint NumberOfDataDirectories; //TODO(size) PEHeaderHeaderDataDirectories but not assert byte count. MAYBE can do math 0x10 x4bytes?
}
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#dll-characteristics
[Ecma("II.25.2.3.2")]
[Flags]
enum DllCharacteristics : ushort
{
Reserved1 = 0x0001,
Reserved2 = 0x0002,
Reserved3 = 0x0004,
Reserved4 = 0x0008,
[Description("The DLL can be relocated at load time.")]
DYNAMIC_BASE = 0x0040,
[Description("Code integrity checks are forced. If you set this flag and a section contains only uninitialized data, set the PointerToRawData member of IMAGE_SECTION_HEADER for that section to zero; otherwise, the image will fail to load because the digital signature cannot be verified.")]
FORCE_INTEGRITY = 0x0080,
[Description("The image is compatible with data execution prevention (DEP).")]
NX_COMPAT = 0x0100,
[Description("The image is isolation aware, but should not be isolated.")]
NO_ISOLATION = 0x0200,
[Description("The image does not use structured exception handling (SEH). No handlers can be called in this image.")]
NO_SEH = 0x0400,
[Description("Do not bind the image. ")]
NO_BIND = 0x0800,
Reserved5 = 0x1000,
[Description("A WDM driver. ")]
WDM_DRIVER = 0x2000,
Reserved6 = 0x4000,
[Description("The image is terminal server aware.")]
TERMINAL_SERVER_AWARE = 0x8000,
}
[Ecma("II.25.2.3.3")]
sealed class PEHeaderHeaderDataDirectories : CodeNode
{
[Description("Always 0.")]
[Expected(0)]
public ulong ExportTable;
[Description("RVA and Size of Import Table.")]
[Ecma("II.25.3.1")]
public RVAandSize ImportTable;
[Description("Always 0, unless resources are compiled in.")]
public ulong ResourceTable;
[Description("Always 0.")]
[Expected(0)]
public ulong ExceptionTable;
[Description("Always 0.")]
[Expected(0)]
public ulong CertificateTable;
[Description("Relocation Table; set to 0 if unused.")] // Typo in spec! Literally just says "(§)"
[Ecma("II.25.3.2")]
public RVAandSize BaseRelocationTable;
[Description("Always 0.")]
//TODO(pedant) What's the right behavior? Multiple expected attributes? [Expected(0)]
public ulong Debug;
[Description("Always 0.")]
[Expected(0)]
public ulong Copyright;
[Description("Always 0.")]
[Expected(0)]
public ulong GlobalPtr;
[Description("Always 0.")]
[Expected(0)]
public ulong TLSTable;
[Description("Always 0.")]
[Expected(0)]
public ulong LoadConfigTable;
[Description("Always 0.")]
[Expected(0)]
public ulong BoundImport;
[Description("RVA and Size of Import Address Table.")]
[Ecma("II.25.3.1")]
public RVAandSize ImportAddressTable;
[Description("Always 0.")]
[Expected(0)]
public ulong DelayImportDescriptor;
[Description("CLI Header with directories for runtime data.")]
[Ecma("II.25.3.1")]
public RVAandSize CLIHeader;
[Description("Always 0.")]
[Expected(0)]
public ulong Reserved;
}
sealed class RVAandSize : CodeNode
{
[OrderedField] public uint RVA;
[OrderedField] public uint Size; //TODO(size)
}
[Ecma("II.25.3")]
sealed class SectionHeader : CodeNode
{
[Description("An 8-byte, null-padded ASCII string. There is no terminating null if the string is exactly eight characters long.")]
public char[] Name;
[Description("Total size of the section in bytes. If this value is greater than SizeOfRawData, the section is zero-padded.")]
public uint VirtualSize; //TODO(size) check if there are any .Error from validating this byte size
[Description("For executable images this is the address of the first byte of the section, when loaded into memory, relative to the image base.")]
public uint VirtualAddress;
[Description("Size of the initialized data on disk in bytes, shall be a multiple of FileAlignment from the PE header. If this is less than VirtualSize the remainder of the section is zero filled. Because this field is rounded while the VirtualSize field is not it is possible for this to be greater than VirtualSize as well. When a section contains only uninitialized data, this field should be 0.")]
public uint SizeOfRawData;
[Description("Offset of section's first page within the PE file. This shall be a multiple of FileAlignment from the optional header. When a section contains only uninitialized data, this field should be 0.")]
public uint PointerToRawData;
[Description("Should be 0.")]
[Expected(0)]
public uint PointerToRelocations;
[Description("Should be 0.")]
[Expected(0)]
public uint PointerToLinenumbers;
[Description("Should be 0.")]
[Expected(0)]
public ushort NumberOfRelocations;
[Description("Should be 0.")]
[Expected(0)]
public ushort NumberOfLinenumbers;
[Description("Flags describing section’s characteristics.")]
public SectionHeaderCharacteristics Characteristics;
protected override int GetCount(string field) => field switch {
nameof(Name) => 8,
_ => base.GetCount(field),
};
}
// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header constants with IMAGE_SCN_
[Flags]
[Ecma("II.25.3")]
enum SectionHeaderCharacteristics : uint
{
[Description("The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES.")]
TYPE_NO_PAD = 0x00000008,
[Description("The section contains executable code.")]
CNT_CODE = 0x00000020,
[Description("The section contains initialized data.")]
CNT_INITIALIZED_DATA = 0x00000040,
[Description("The section contains uninitialized data.")]
CNT_UNINITIALIZED_DATA = 0x00000080,
[Description("Reserved.")]
LNK_OTHER = 0x00000100,
[Description("The section contains comments or other information. This is valid only for object files.")]
LNK_INFO = 0x00000200,
[Description("The section will not become part of the image. This is valid only for object files.")]
LNK_REMOVE = 0x00000800,
[Description("The section contains COMDAT data. This is valid only for object files.")]
LNK_COMDAT = 0x00001000,
[Description("Reset speculative exceptions handling bits in the TLB entries for this section.")]
NO_DEFER_SPEC_EXC = 0x00004000,
[Description("The section contains data referenced through the global pointer.")]
GPREL = 0x00008000,
[Description("Reserved.")]
MEM_PURGEABLE = 0x00020000,
[Description("Reserved.")]
MEM_LOCKED = 0x00040000,
[Description("Reserved.")]
MEM_PRELOAD = 0x00080000,
[Description("Align data on a 1-byte boundary. This is valid only for object files.")]
ALIGN_1BYTES = 0x00100000,
[Description("Align data on a 2-byte boundary. This is valid only for object files.")]
ALIGN_2BYTES = 0x00200000,
[Description("Align data on a 4-byte boundary. This is valid only for object files.")]
ALIGN_4BYTES = 0x00300000,
[Description("Align data on a 8-byte boundary. This is valid only for object files.")]
ALIGN_8BYTES = 0x00400000,
[Description("Align data on a 16-byte boundary. This is valid only for object files.")]
ALIGN_16BYTES = 0x00500000,
[Description("Align data on a 32-byte boundary. This is valid only for object files.")]
ALIGN_32BYTES = 0x00600000,
[Description("Align data on a 64-byte boundary. This is valid only for object files.")]
ALIGN_64BYTES = 0x00700000,
[Description("Align data on a 128-byte boundary. This is valid only for object files.")]
ALIGN_128BYTES = 0x00800000,
[Description("Align data on a 256-byte boundary. This is valid only for object files.")]
ALIGN_256BYTES = 0x00900000,
[Description("Align data on a 512-byte boundary. This is valid only for object files.")]
ALIGN_512BYTES = 0x00A00000,
[Description("Align data on a 1024-byte boundary. This is valid only for object files.")]
ALIGN_1024BYTES = 0x00B00000,
[Description("Align data on a 2048-byte boundary. This is valid only for object files.")]
ALIGN_2048BYTES = 0x00C00000,
[Description("Align data on a 4096-byte boundary. This is valid only for object files.")]
ALIGN_4096BYTES = 0x00D00000,
[Description("Align data on a 8192-byte boundary. This is valid only for object files.")]
ALIGN_8192BYTES = 0x00E00000,
[Description("The section contains extended relocations. The count of relocations for the section exceeds the 16 bits that is reserved for it in the section header. If the NumberOfRelocations field in the section header is 0xffff, the actual relocation count is stored in the VirtualAddress field of the first relocation. It is an error if IMAGE_SCN_LNK_NRELOC_OVFL is set and there are fewer than 0xffff relocations in the section.")]
LNK_NRELOC_OVFL = 0x01000000,
[Description("The section can be discarded as needed.")]
MEM_DISCARDABLE = 0x02000000,
[Description("The section cannot be cached.")]
MEM_NOT_CACHED = 0x04000000,
[Description("The section cannot be paged.")]
MEM_NOT_PAGED = 0x08000000,
[Description("The section can be shared in memory.")]
MEM_SHARED = 0x10000000,
[Description("The section can be executed as code.")]
MEM_EXECUTE = 0x20000000,
[Description("The section can be read.")]
MEM_READ = 0x40000000,
[Description("The section can be written to.")]
MEM_WRITE = 0x80000000,
}
[Ecma("II.25.3")]
sealed class Section : CodeNode
{
int rva;
public CLIHeader CLIHeader;
public MetadataRoot MetadataRoot;
public StringHeap StringHeap;
public UserStringHeap UserStringHeap;
public BlobHeap BlobHeap;
public GuidHeap GuidHeap;
public TildeStream TildeStream;
public ResourceEntry[] ResourceEntries;
public ImportTable ImportTable;
public ImportLookupTable ImportLookupTable;
public ImportAddressHintNameTable ImportAddressHintNameTable;
public NullTerminatedString RuntimeEngineName = new NullTerminatedString(Encoding.ASCII, 1);
public NativeEntryPoint NativeEntryPoint;
public ImportAddressTable ImportAddressTable;
public Relocations Relocations;
public Methods Methods;
SectionHeader header;
public Section(SectionHeader header) {
this.header = header;
}
protected override void InnerRead() {
Start = (int)header.PointerToRawData;
header.Child(nameof(header.PointerToRawData)).Link = this;
End = Start + (int)header.SizeOfRawData;
rva = (int)header.VirtualAddress;
string name = new string(header.Name);
Description = name;
var optionalHeader = Bytes.FileFormat.PEHeader.PEOptionalHeader;
var dataDirs = optionalHeader.PEHeaderHeaderDataDirectories;
foreach (var nr in dataDirs.GetType().GetFields()
.Where(field => field.FieldType == typeof(RVAandSize))
.Select(field => new { name = field.Name, rva = (RVAandSize)field.GetValue(dataDirs) })
.Where(nr => nr.rva.RVA > 0)
.Where(nr => rva <= nr.rva.RVA && nr.rva.RVA < rva + End - Start)
.OrderBy(nr => nr.rva.RVA)) {
LinkReposition(nr.rva, nameof(nr.rva.RVA));
switch (nr.name) {
case "CLIHeader":
Bytes.CLIHeaderSection = this;
AddChild(nameof(CLIHeader));
LinkReposition(CLIHeader.MetaData, nameof(CLIHeader.MetaData.RVA));
AddChild(nameof(MetadataRoot));
foreach (var streamHeader in MetadataRoot.StreamHeaders.OrderBy(h => h.Name.Str.IndexOf('~'))) // Read #~ after heaps
{
LinkReposition(streamHeader.Child(nameof(streamHeader.Offset)), streamHeader.Offset + CLIHeader.MetaData.RVA);
switch (streamHeader.Name.Str) {
case "#Strings":
StringHeap = new StringHeap((int)streamHeader.Size);
AddChild(nameof(StringHeap));
break;
case "#US":
UserStringHeap = new UserStringHeap((int)streamHeader.Size);
AddChild(nameof(UserStringHeap));
break;
case "#Blob":
BlobHeap = new BlobHeap((int)streamHeader.Size);
AddChild(nameof(BlobHeap));
break;
case "#GUID":
GuidHeap = new GuidHeap((int)streamHeader.Size);
AddChild(nameof(GuidHeap));
break;
case "#~":
TildeStream = new TildeStream(this);
AddChild(nameof(TildeStream));
ReadManifestResources();
AddChild(nameof(Methods));
if (!Methods.Children.Any()) {
Children.RemoveAt(Children.Count - 1); // Don't ResizeLastChild
}
break;
default:
Errors.Add("Unexpected stream name: " + streamHeader.Name.Str);
break;
}
}
break;
case "ImportTable":
AddChild(nameof(ImportTable));
LinkReposition(ImportTable, nameof(ImportTable.ImportLookupTable));
AddChild(nameof(ImportLookupTable));
LinkReposition(ImportLookupTable, nameof(ImportLookupTable.HintNameTableRVA));
AddChild(nameof(ImportAddressHintNameTable));
LinkReposition(ImportTable, nameof(ImportTable.Name));
AddChild(nameof(RuntimeEngineName));
var standardFields = optionalHeader.PEHeaderStandardFields;
LinkReposition(standardFields, nameof(standardFields.EntryPointRVA));
AddChild(nameof(NativeEntryPoint));
break;
case "ImportAddressTable":
AddChild(nameof(ImportAddressTable));
break;
case "BaseRelocationTable":
AddChild(nameof(Relocations));
break;
default:
throw new NotImplementedException($"{name} {nr.name}");
}
}
}
void ReadManifestResources() {
if (TildeStream.ManifestResources == null) return;
var entries = new List<ResourceEntry>();
for (int i = 0; i < TildeStream.ManifestResources.Length; ++i) {
var manifest = TildeStream.ManifestResources[i];
// TODO(pedant) .Implementation not-null means the resource entry is in another file
LinkReposition(manifest.Child(nameof(manifest.Offset)), manifest.Offset + CLIHeader.Resources.RVA);
var entry = Bytes.ReadClass<ResourceEntry>();
entry.NodeName = $"{nameof(ResourceEntries)}[{i}]";
entries.Add(entry);
}
ResourceEntries = entries.ToArray();
Children.AddRange(entries);
}
public void RepositionWithoutLink(long dataRVA) => Bytes.Stream.Position = Start + dataRVA - rva;
public void LinkReposition(CodeNode parent, string childName) {
var child = parent.Child(childName);
LinkReposition(child, child.GetInt32());
}
public void LinkReposition(CodeNode linker, long dataRVA) {
Bytes.PendingLink = linker;
RepositionWithoutLink(dataRVA);
}
}
[Ecma("II.25.3.1")]
sealed class ImportTable : CodeNode
{
[Description("RVA of the Import Lookup Table")]
public uint ImportLookupTable;
[Description("Always 0.")]
[Expected(0)]
public uint DateTimeStamp;
[Description("Always 0.")]
[Expected(0)]
public uint ForwarderChain;
[Description("RVA of null-terminated ASCII string “mscoree.dll”.")]
public uint Name;
[Description("RVA of Import Address Table (this is the same as the RVA of the IAT descriptor in the optional header).")]
public uint ImportAddressTableRVA; //TODO(link)
[Description("End of Import Table. Shall be filled with zeros.")]
[Expected(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00})]
public byte[] Reserved;
}
[Ecma("II.25.3.1")]
sealed class ImportAddressTable : CodeNode
{
[OrderedField]
public uint HintNameTableRVA; //TODO(link)
[Expected(0)]
public uint NullTerminated;
}
[Ecma("II.25.3.1")]
sealed class ImportLookupTable : CodeNode
{
[OrderedField]
public uint HintNameTableRVA;
[Expected(0)]
public uint NullTerminated;
}
[Ecma("II.25.3.1")]
sealed class ImportAddressHintNameTable : CodeNode
{
[Description("Shall be 0.")]
[Expected(0)]
public ushort Hint;
[Description("Case sensitive, null-terminated ASCII string containing name to import. Shall be “_CorExeMain” for a.exe file and “_CorDllMain” for a.dll file.")]
public char[] Message;
protected override int GetCount(string field) => field switch {
nameof(Message) => 12,
_ => base.GetCount(field),
};
}
// It would be nice to parse all x86 or other kind of assemblies like ARM or JAR, but that's out-of-scope
[Ecma("II.25.2.3.1")]
sealed class NativeEntryPoint : CodeNode
{
[Description("JMP op code in X86")]
[Expected(0xFF)]
public byte JMP;
[Description("Specifies the jump is in absolute indirect mode")]
[Expected(0x25)]
public byte Mod;
[Description("Jump target RVA.")]
public uint JumpTarget;
}
[Ecma("II.25.3.2")]
sealed class Relocations : CodeNode
{
[OrderedField]
public BaseRelocationTable BaseRelocationTable;
[OrderedField]
public Fixup[] Fixups;
protected override int GetCount(string field) {
switch (field) {
case nameof(Fixups):
var n = ((int)BaseRelocationTable.BlockSize - 8) / 2;
if (n >= 0) return n;
Errors.Add($"Calculated Fixups length {n} was negative!");
return 0;
default: return base.GetCount(field);
}
}
}
[Ecma("II.25.3.2")]
sealed class BaseRelocationTable : CodeNode
{
[OrderedField] public uint PageRVA;
[OrderedField] public uint BlockSize;
}
[Ecma("II.25.3.2")]
sealed class Fixup : CodeNode
{
[Description("Stored in high 4 bits of word, type IMAGE_REL_BASED_HIGHLOW (0x3).")]
public byte Type;
[Description("Stored in remaining 12 bits of word. Offset from starting address specified in the Page RVA field for the block. This offset specifies where the fixup is to be applied.")]
public short Offset;
protected override CodeNode ReadField(string fieldName) { // MAYBE just override Read() and no children
switch (fieldName) {
case nameof(Type):
var type = new StructNode<byte> { Bytes = Bytes };
type.Read();
Offset = (short)((type.t << 8) & 0x0F00);
Type = (byte)(type.t >> 4);
type.NodeValue = Type.GetString();
return type;
case nameof(Offset):
var offset = new StructNode<byte> { Bytes = Bytes };
offset.Read();
Offset |= (short)offset.t;
offset.NodeValue = Offset.GetString();
return offset;
default:
throw new InvalidOperationException();
}
}
}
[Ecma("II.25.3.3")]
sealed class CLIHeader : CodeNode
{
[Description("Size of the header in bytes")]
public uint Cb; //TODO(size) rename to CbHeaderSize
[Description("The minimum version of the runtime required to run this program, currently 2.")]
public ushort MajorRuntimeVersion;
[Description("The minor portion of the version, currently 0.")]
public ushort MinorRuntimeVersion;
[Description("RVA and size of the physical metadata.")]
[Ecma("II.24")]
public RVAandSize MetaData;
[Description("Flags describing this runtime image.")]
public CliHeaderFlags Flags;
[Description("Token for the MethodDef or File of the entry point for the image.")]
[Ecma("II.25.3.3.2")]
public uint EntryPointToken; //TODO(link) should this be MetadataToken?
[Description("RVA and size of implementation-specific resources.")]
[Ecma("II.25.3.3")]
public RVAandSize Resources;
[Description("RVA of the hash data for this PE file used by the CLI loader for binding and versioning.")]
[Ecma("II.25.3.3.4")]
public RVAandSize StrongNameSignature;
[Description("Always 0.")]
[Expected(0)]
public ulong CodeManagerTable;
[Description("RVA of an array of locations in the file that contain an array of function pointers (e.g., vtable slots).")]
[Ecma("II.25.3.3.3")]
public RVAandSize VTableFixups;
[Description("Always 0.")]
[Expected(0)]
public ulong ExportAddressTableJumps;
[Description("Always 0.")]
[Expected(0)]
public ulong ManagedNativeHeader;
}
[Flags]
[Ecma("II.25.3.3.1")]
enum CliHeaderFlags : uint
{
[Description("Shall be 1.")]
ILOnly = 0x01,
[Description("Image can only be loaded into a 32-bit process, for instance if there are 32-bit vtablefixups, or casts from native integers to int32. CLI implementations that have 64-bit native integers shall refuse loading binaries with this flag set.")]
Required32Bit = 0x02,
[Description("Image has a strong name signature.")]
StrongNameSigned = 0x08,
[Description("Shall be 0.")]
NativeEntryPoint = 0x10,
[Description("Should be 0.")]
TrackDebugData = 0x10000,
}
[Ecma("II.25.4")]
sealed class Methods : CodeNode
{
protected override void InnerRead() {
if (Bytes.TildeStream.MethodDefs == null) return;
var methodDefs = Bytes.TildeStream.MethodDefs.GroupBy(m => m.RVA).OrderBy(g => g.Key);
foreach (var methodDefGroup in methodDefs) {
var rva = methodDefGroup.Key;
if (rva == 0) continue;
Bytes.CLIHeaderSection.RepositionWithoutLink(rva);
var method = Bytes.ReadClass<Method>();
foreach (var methodDef in methodDefGroup) {
methodDef.Child(nameof(methodDef.RVA)).Link = method;
}
method.NodeName = $"{nameof(Method)}[{Children.Count}]";
Children.Add(method);
}
Start = Children.Min(m => m.Start);
End = Children.Max(m => m.End);
}
}
[Ecma("II.25.4")]
sealed class Method : CodeNode
{
public byte Header; //TODO(pedant) ? enum
public FatFormat FatFormat;
public MethodDataSection[] DataSections;
public InstructionStream CilOps;
public int CodeSize { get; private set; }
public int MaxStack { get; private set; }
protected override void InnerRead() {
AddChild(nameof(Header));
var header = Children.Single();
var moreSects = false;
var type = (MethodHeaderType)(Header & 0x03);
switch (type) {
case MethodHeaderType.Tiny:
CodeSize = Header >> 2;
MaxStack = 8;
header.Description = $"Tiny Header, 0x{CodeSize:X} bytes long";
header.EcmaSection = "II.25.4.2";
break;
case MethodHeaderType.Fat:
AddChild(nameof(FatFormat));
if ((FatFormat.FlagsAndSize & 0xF0) != 0x30) {
Errors.Add("Expected upper bits of FlagsAndSize to be 3");
}
CodeSize = (int)FatFormat.CodeSize;
MaxStack = FatFormat.MaxStack;
header.Description = $"Fat Header, 0x{CodeSize:X} bytes long";
moreSects = ((MethodHeaderType)Header).HasFlag(MethodHeaderType.MoreSects);
break;
default:
throw new InvalidOperationException("Invalid MethodHeaderType " + type);
}
CilOps = new InstructionStream(this) { Bytes = Bytes };
AddChild(nameof(CilOps));
if (moreSects) {
while (Bytes.Stream.Position % 4 != 0) {
_ = Bytes.Read<byte>();
}
var dataSections = Bytes.ReadClass<MethodDataSections>();
dataSections.NodeName = "MethodDataSections";
Children.Add(dataSections.Children.Count == 1 ? dataSections.Children.Single() : dataSections);
}
}
}
sealed class MethodDataSections : CodeNode
{
protected override void InnerRead() {
MethodDataSection dataSection = null;
do {
dataSection = Bytes.ReadClass<MethodDataSection>();
dataSection.NodeName = $"{nameof(MethodDataSections)}[{Children.Count}]";
Children.Add(dataSection);
}
while (dataSection.MethodHeaderSection.HasFlag(MethodHeaderSection.MoreSects));
}
}
[Ecma("II.25.4.1")] // also .4 but we'll ignore that
enum MethodHeaderType : byte
{
Tiny = 0x02,
Fat = 0x03,
MoreSects = 0x08,
InitLocals = 0x10,
}
[Ecma("II.25.4.3")]
sealed class FatFormat : CodeNode
{
[Description("Lower four bits is rest of Flags, Upper four bits is size of this header expressed as the count of 4-byte integers occupied (currently 3)")]
public byte FlagsAndSize;
[Description("Maximum number of items on the operand stack")]
public ushort MaxStack;
[Description("Size in bytes of the actual method body")]
public uint CodeSize;
[Description("Meta Data token for a signature describing the layout of the local variables for the method")]
public MetadataToken LocalVarSigTok; // MAYBE is "Meta Data token? defined somewhere
}
[Ecma("II.25.4.5")]
sealed class MethodDataSection : CodeNode
{
public MethodHeaderSection MethodHeaderSection;
public LargeMethodHeader LargeMethodHeader;
public SmallMethodHeader SmallMethodHeader;
protected override void InnerRead() {
AddChild(nameof(MethodHeaderSection));
if (!MethodHeaderSection.HasFlag(MethodHeaderSection.EHTable)) {
throw new InvalidOperationException("Only kind of section data is exception header");
}
if (MethodHeaderSection.HasFlag(MethodHeaderSection.FatFormat)) {
AddChild(nameof(LargeMethodHeader));
} else {
AddChild(nameof(SmallMethodHeader));
}
}
}
[Flags]
[Ecma("II.25.4.5")]
enum MethodHeaderSection : byte
{