-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathindex.HTM
1840 lines (1795 loc) · 54.1 KB
/
index.HTM
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
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta http-equiv="Content-Language" content="zh-cn">
<title>PE格式文件解读</title>
</head>
<body>
<h1><a></a>1 基本概念</h1>
<p>下表描述了贯穿于本文中的一些概念:</p>
<table border="1" cellspacing="1">
<tr>
<td width="65" align="center">名称</td>
<td align="center">描述</td>
</tr>
<tr>
<td width="65">地址</td>
<td>是“虚拟地址”而不是“物理地址”。为什么不是“物理地址”呢?因为数据在内存的位置经常在变,这样可以节省内存开支、避开错误的内存位置等的优势。同时用户并不需要知道具体的“真实地址”,因为系统自己会为程序准备好内存空间的(只要内存足够大)</td>
</tr>
<tr>
<td width="65">镜像文件</td>
<td>包含以EXE文件为代表的“可执行文件”、以DLL文件为代表的“动态链接库”。为什么用“镜像”?这是因为他们常常被直接“复制”到内存,有“镜像”的某种意思。看来西方人挺有想象力的哦^0^</td>
</tr>
<tr>
<td width="65">RVA</td>
<td>英文全称Relatively Virtual Address。偏移(又称“相对虚拟地址”)。相对镜像基址的偏移。</td>
</tr>
<tr>
<td width="65">节</td>
<td>节是PE文件中代码或数据的基本单元。原则上讲,节只分为“代码节”和“数据节”。</td>
</tr>
<tr>
<td width="65">VA</td>
<td>英文全称Virtual Address。基址</td>
</tr>
</table>
<h1><a name="2"></a>2 概览</h1>
<p>x86都是32位的,IA-64都是64位的。64位Windows需要做的只是修改PE格式的少数几个域。这种新的格式被称为PE32+。它并没有增加任何新域,仅从PE格式中删除了一个域。其余的改变就是简单地把某些域从32位扩展到64位。在大部分情况下,你都能写出同时适用于32位和64位PE文件的代码。</p>
<p>EXE文件与DLL文件的区别完全是语义上的。它们使用的是相同的PE格式。惟一的不同在于一个位,这个位用来指示文件应该作为EXE还是DLL。甚至DLL文件的扩展名也完全也是人为的。你可以给DLL一个完全不同的扩展名,例如.OCX控件和控制面板小程序(.CPL)都是DLL。</p>
<p align="center">
<img border="0" src="pe.gif" width="332" height="443"></p>
<p align="center">图1 解释了Microsoft PE可执行文件格式:</p>
<p>PE文件总体上分为“头”和“节”。“头”是“节”的描述、简化、说明,“节”是“头”的具体化。</p>
<h1><a name="3"></a>3 文件头</h1>
<p>PE文件的头分为DOS头、NT头、节头。注意,这是本人的分法,在此之前并没有这种分法。这样分法会更加合理,更易理解。因为这三个部分正好构成SizeOfHeaders所指的范围,所以将它们合为“头”。这里的3个头与别的文章的头的定义会有所区别。</p>
<p>节头紧跟在NT头后面。</p>
<h2><a name="3.1"></a>3.1 DOS头(<b>PE文件签名的偏移地址</b>就是大小)</h2>
<p>用记事本打开任何一个镜像文件,其头2个字节必为字符串“MZ”,这是Mark Zbikowski的姓名缩写,他是最初的MS-DOS设计者之一。然后是一些在MS-DOS下的一些参数,这些参数是在MS-DOS下运行该程序时要用到的。在这些参数的末尾也就是文件的偏移0x3C(第60字节)处是是一个4字节的<b>PE文件签名的偏移地址</b>。该地址有一个专用名称叫做“E_lfanew”。这个签名是“PE00”(字母“P”和“E”后跟着两个<b>空字节</b>)。紧跟着E_lfanew的是一个MS-DOS程序。那是一个运行于MS-DOS下的合法应用程序。当可执行文件(一般指exe、com文件)运行于MS-DOS下时,这个程序显示“This program cannot be run in DOS mode(此程序不能在DOS模式下运行)”这条消息。用户也可以自己更改该程序,有些还原软件就是这么干的。同时,有些程序既能运行于DOS又能运行于Windows下就是这个原因。Notepad.exe整个DOS头大小为224个字节,大部分不能在DOS下运行的Win32文件都是这个值。MS-DOS程序是可有可无的,如果你想使文件大小尽可能的小可以省掉MS-DOS程序,同时把前面的参数都清0。</p>
<h2><a name="3.2"></a>3.2 NT头(244或260个字节)</h2>
<p>紧跟着PE文件签名之后,是NT头。NT头分成3个部分,因为第2部分在32与64位系统里有区别,第3部分虽然也是头,但实际很不像“头”。</p>
<p>第1部分(20个字节)</p>
<table border="1" width="985" cellspacing="1">
<tr>
<td width="40" align="center">偏移</td>
<td width="40" align="center">大小</td>
<td width="160" align="center">英文名</td>
<td align="center" width="110">中文名</td>
<td align="center" width="623">描述</td>
</tr>
<tr>
<td width="40">0</td>
<td width="40">2</td>
<td width="160">Machine</td>
<td width="110">机器数</td>
<td width="623">标识CPU的数字。参考3.2.1节“<a href="#3.2.1">机器类型</a>”。</td>
</tr>
<tr>
<td width="40">2</td>
<td width="40">2</td>
<td width="160">NumberOfSections</td>
<td width="110">节数</td>
<td width="623">节的数目。Windows加载器限制节的最大数目为96。</td>
</tr>
<tr>
<td width="40">4</td>
<td width="40">4</td>
<td width="160">TimeDateStamp</td>
<td width="110">时间/日期标记</td>
<td width="623">UTC时间1970年1月1日00:00起的总秒数的低32位,它指出文件何时被创建。</td>
</tr>
<tr>
<td width="40">8</td>
<td width="40">8</td>
<td width="480" colspan="3">已经废除</td>
</tr>
<tr>
<td width="40">16</td>
<td width="40">2</td>
<td width="160">SizeOfOptionalHeader</td>
<td width="110">可选头大小</td>
<td width="623">第2部分+第3部分的总大小。这个大小在32位和64位文件中是不同的。对于32位文件来说,它是224;对于64位文件来说,它是240。</td>
</tr>
<tr>
<td width="40">18</td>
<td width="40">2</td>
<td width="160">FillCharacteristics</td>
<td width="110">文件特征值</td>
<td width="623">指示文件属性的标志。参考3.2.2节“<a href="#3.2.2">特征</a>”。</td>
</tr>
</table>
<p>第2部分(96或112个字节)</p>
<table border="1" cellspacing="1">
<tr>
<td align="center" width="48">偏移</td>
<td width="32" align="center">大小</td>
<td align="center" width="216">英文名</td>
<td align="center" width="135">中文名</td>
<td align="center">描述</td>
</tr>
<tr>
<td width="48">0</td>
<td width="32">2</td>
<td width="216">Magic</td>
<td width="135">魔数</td>
<td>这个无符号整数指出了镜像文件的状态。<br>
0x10B表明这是一个32位镜像文件。<br>
0x107表明这是一个ROM镜像。<br>
0x20B表明这是一个64位镜像文件。</td>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>MajorLinkerVersion</td>
<td>链接器的主版本号</td>
<td>链接器的主版本号。</td>
</tr>
<tr>
<td>3</td>
<td>1</td>
<td>MinorLinkerVersion</td>
<td>链接器的次版本号</td>
<td>链接器的次版本号。</td>
</tr>
<tr>
<td>4</td>
<td>4</td>
<td>SizeOfCode</td>
<td>代码节大小</td>
<td>一般放在“.text”节里。如果有多个代码节的话,它是所有代码节的和。必须是FileAlignment的整数倍,是在文件里的大小。</td>
</tr>
<tr>
<td>8</td>
<td>4</td>
<td>SizeOfInitializedData</td>
<td>已初始化数大小</td>
<td>一般放在“.data”节里。如果有多个这样的节话,它是所有这些节的和。必须是FileAlignment的整数倍,是在文件里的大小。</td>
</tr>
<tr>
<td>12</td>
<td>4</td>
<td>SizeOfUninitializedData</td>
<td>未初始化数大小</td>
<td>一般放在“.bss”节里。如果有多个这样的节话,它是所有这些节的和。必须是FileAlignment的整数倍,是在文件里的大小。</td>
</tr>
<tr>
<td>16</td>
<td>4</td>
<td>AddressOfEntryPoint</td>
<td>入口点</td>
<td>当可执行文件被加载进内存时其入口点RVA。对于一般程序镜像来说,它就是启动地址。为0则从ImageBase开始执行。对于dll文件是可选的。</td>
</tr>
<tr>
<td>20</td>
<td>4</td>
<td>BaseOfCode</td>
<td>代码基址</td>
<td>当镜像被加载进内存时代码节的开头RVA。必须是SectionAlignment的整数倍。</td>
</tr>
<tr>
<td>24</td>
<td>4</td>
<td>BaseOfData</td>
<td>数据基址</td>
<td>当镜像被加载进内存时数据节的开头RVA。(在64位文件中此处被并入紧随其后的ImageBase中。)必须是SectionAlignment的整数倍。</td>
</tr>
<tr>
<td>28/24</td>
<td>4/8</td>
<td>ImageBase</td>
<td><b><font size="5" color="#0000FF">镜像基址</font></b></td>
<td>当加载进内存时镜像的第1个字节的首选地址。它必须是64K的倍数。DLL默认是10000000H。Windows CE 的EXE默认是00010000H。Windows 系列的EXE默认是00400000H。</td>
</tr>
<tr>
<td>32</td>
<td>4</td>
<td>SectionAlignment</td>
<td>内存对齐</td>
<td>当加载进内存时节的对齐值(以字节计)。它必须≥FileAlignment。默认是相应系统的页面大小。</td>
</tr>
<tr>
<td>36</td>
<td>4</td>
<td>FileAlignment</td>
<td>文件对齐</td>
<td>用来对齐镜像文件的节中的原始数据的对齐因子(以字节计)。它应该是界于512和64K之间的2的幂(包括这两个边界值)。默认是512。如果<b>SectionAlignment</b>小于相应系统的页面大小,那么FileAlignment必须与SectionAlignment相等。</td>
</tr>
<tr>
<td>40</td>
<td>2</td>
<td>MajorOperatingSystemVersion</td>
<td>主系统的主版本号</td>
<td>操作系统的版本号可以从“我的电脑”→“帮助”里面看到,Windows XP是5.1。5是主版本号,1是次版本号</td>
</tr>
<tr>
<td>42</td>
<td>2</td>
<td>MinorOperatingSystemVersion</td>
<td>主系统的次版本号</td>
<td> </td>
</tr>
<tr>
<td>44</td>
<td>2</td>
<td>MajorImageVersion</td>
<td>镜像的主版本号</td>
<td> </td>
</tr>
<tr>
<td>46</td>
<td>2</td>
<td>MinorImageVersion</td>
<td>镜像的次版本号</td>
<td> </td>
</tr>
<tr>
<td>48</td>
<td>2</td>
<td>MajorSubsystemVersion</td>
<td>子系统的主版本号</td>
<td> </td>
</tr>
<tr>
<td>50</td>
<td>2</td>
<td>MinorSubsystemVersion</td>
<td>子系统的次版本号</td>
<td> </td>
</tr>
<tr>
<td>52</td>
<td>4</td>
<td>Win32VersionValue</td>
<td>保留,必须为0</td>
<td> </td>
</tr>
<tr>
<td>56</td>
<td>4</td>
<td>SizeOfImage</td>
<td>镜像大小</td>
<td>当镜像被加载进内存时的大小,包括所有的文件头。向上舍入为SectionAlignment的倍数。</td>
</tr>
<tr>
<td>60</td>
<td>4</td>
<td>SizeOfHeaders</td>
<td>头大小</td>
<td>所有头的总大小,向上舍入为FileAlignment的倍数。可以以此值作为PE文件第一节的文件偏移量。</td>
</tr>
<tr>
<td>64</td>
<td>4</td>
<td>CheckSum</td>
<td>校验和</td>
<td>镜像文件的校验和。计算校验和的算法被合并到了Imagehlp.DLL 中。以下程序在加载时被校验以确定其是否合法:所有的驱动程序、任何在引导时被加载的DLL以及加载进关键Windows进程中的DLL。</td>
</tr>
<tr>
<td>68</td>
<td>2</td>
<td>Subsystem</td>
<td>子系统类型</td>
<td>运行此镜像所需的子系统。参考后面的“<a href="#3.2.3">Windows子系统</a>”部分。</td>
</tr>
<tr>
<td>70</td>
<td>2</td>
<td>DllCharacteristics</td>
<td>DLL标识</td>
<td>参考后面的“<a href="#3.2.4">DLL特征</a>”部分。</td>
</tr>
<tr>
<td>72</td>
<td>4/8</td>
<td>SizeOfStackReserve</td>
<td>堆栈保留大小</td>
<td>最大<b>栈</b>大小。<b>CPU的堆栈</b>。默认是1MB。</td>
</tr>
<tr>
<td>76/80</td>
<td>4/8</td>
<td>SizeOfStackCommit</td>
<td>堆栈提交大小</td>
<td>初始提交的堆栈大小。默认是4KB。</td>
</tr>
<tr>
<td>80/88</td>
<td>4/8</td>
<td>SizeOfHeapReserve</td>
<td>堆保留大小</td>
<td>最大<b>堆</b>大小。<b>编译器分配的</b>。默认是1MB。</td>
</tr>
<tr>
<td>84/96</td>
<td>4/8</td>
<td>SizeOfHeapCommit</td>
<td>堆栈交大小</td>
<td>初始提交的局部堆空间大小。默认是4KB。</td>
</tr>
<tr>
<td>88/104</td>
<td>4</td>
<td>LoaderFlags</td>
<td>保留,必须为0</td>
<td> </td>
</tr>
<tr>
<td>92/108</td>
<td>4</td>
<td>NumberOfRvaAndSizes</td>
<td>目录项数目</td>
<td>数据目录项的个数。由于以前发行的Windows NT的原因,它只能为16。</td>
</tr>
</table>
<p>第3部分数据目录(128个字节)</p>
<table border="1" cellspacing="1">
<tr>
<td align="center">
<p align="center">偏移<br>(PE32/PE32+)</td>
<td align="center">大小</td>
<td align="center">英文名</td>
<td align="center">描述</td>
</tr>
<tr>
<td>96/112</td>
<td>8</td>
<td>Export Table</td>
<td><b>导出表</b>的地址和大小。参考5.1节“<a href="#5.1">.edata</a>”</td>
</tr>
<tr>
<td>104/120</td>
<td>8</td>
<td>Import Table</td>
<td><b>导入目录表</b>的地址和大小。参考5.2.1节“<a href="#5.2">.idata</a>”</td>
</tr>
<tr>
<td>112/128</td>
<td>8</td>
<td>Resource Table</td>
<td><b>资源表</b>的地址和大小。参考5.6节“<a href="#5.6">.rsrc</a>”</td>
</tr>
<tr>
<td>120/136</td>
<td>8</td>
<td>Exception Table</td>
<td><b>异常表</b>的地址和大小。参考5.3节“<a href="#5.3">.pdata</a>”</td>
</tr>
<tr>
<td>128/144</td>
<td>8</td>
<td>Certificate Table</td>
<td><b>属性证书表</b>的地址和大小。参考6节“<a href="#6">属性证书表</a>”</td>
</tr>
<tr>
<td>136/152</td>
<td>8</td>
<td>Base Relocation Table</td>
<td><b>基址重定位表</b>的地址和大小。参考5.4节“<a href="#5.4">.reloc</a>”</td>
</tr>
<tr>
<td>144/160</td>
<td>8</td>
<td>Debug</td>
<td>调试数据起始地址和大小。</td>
</tr>
<tr>
<td>152/168</td>
<td>8</td>
<td>Architecture</td>
<td>保留,必须为0</td>
</tr>
<tr>
<td>160/176</td>
<td>8</td>
<td>Global Ptr</td>
<td>将被存储在全局指针寄存器中的一个值的RVA。<b>这个结构的Size域必须为0</b></td>
</tr>
<tr>
<td>168/184</td>
<td>8</td>
<td>TLS Table</td>
<td><b>线程局部存储(TLS)表</b>的地址和大小。</td>
</tr>
<tr>
<td>176/192</td>
<td>8</td>
<td>Load Config Table</td>
<td><b>加载配置表</b>的地址和大小。参考5.5节“<a href="#5.5">加载配置结构</a>”</td>
</tr>
<tr>
<td>184/200</td>
<td>8</td>
<td>Bound Import</td>
<td><b>绑定导入查找表</b>的地址和大小。参考5.2.2节“<a href="#5.2.4">导入查找表</a>”</td>
</tr>
<tr>
<td>192/208</td>
<td>8</td>
<td>IAT</td>
<td><b>导入地址表</b>的地址和大小。参考5.2.4节“<a href="#5.2.4">导入地址表</a>”</td>
</tr>
<tr>
<td>200/216</td>
<td>8</td>
<td>Delay Import Descriptor</td>
<td><b>延迟导入描述符</b>的地址和大小。</td>
</tr>
<tr>
<td>208/224</td>
<td>8</td>
<td>CLR Runtime Header</td>
<td>CLR运行时头部的地址和大小。(已废除)</td>
</tr>
<tr>
<td>216/232</td>
<td>8</td>
<td colspan="2">
<p align="center">保留,必须为0</p></td>
</tr>
</table>
<h3><a name="3.2.1"></a>3.2.1 机器类型</h3>
<p>Machine域可以取以下各值中的一个来指定CPU类型。镜像文件仅能运行于指定处理器或者能够模拟指定处理器的系统上。</p>
<table border="1" cellspacing="1">
<tr>
<td align="center">值</td>
<td align="center">描述</td>
</tr>
<tr>
<td>0x0</td>
<td>适用于任何类型处理器</td>
</tr>
<tr>
<td>0x1d3</td>
<td>Matsushita AM33处理器</td>
</tr>
<tr>
<td height="22">0x8664</td>
<td height="22">x64处理器</td>
</tr>
<tr>
<td>0x1c0</td>
<td>ARM小尾处理器</td>
</tr>
<tr>
<td>0xebc</td>
<td>EFI字节码处理器</td>
</tr>
<tr>
<td><font color="red"><b>0x14c</b></font></td>
<td><font color="red"><b>Intel 386或后继处理器及其兼容处理器</b></font></td>
</tr>
<tr>
<td>0x200</td>
<td>Intel Itanium处理器</td>
</tr>
<tr>
<td>0x9041</td>
<td>Mitsubishi M32R小尾处理器</td>
</tr>
<tr>
<td>0x266</td>
<td>MIPS16处理器</td>
</tr>
<tr>
<td>0x366</td>
<td>带FPU的MIPS处理器</td>
</tr>
<tr>
<td>0x466</td>
<td>带FPU的MIPS16处理器</td>
</tr>
<tr>
<td>0x1f0</td>
<td>PowerPC小尾处理器</td>
</tr>
<tr>
<td>0x1f1</td>
<td>带符点运算支持的PowerPC处理器</td>
</tr>
<tr>
<td>0x166</td>
<td>MIPS小尾处理器</td>
</tr>
<tr>
<td>0x1a2</td>
<td>Hitachi SH3处理器</td>
</tr>
<tr>
<td>0x1a3</td>
<td>Hitachi SH3 DSP处理器</td>
</tr>
<tr>
<td>0x1a6</td>
<td>Hitachi SH4处理器</td>
</tr>
<tr>
<td>0x1a6</td>
<td>Hitachi SH5处理器</td>
</tr>
<tr>
<td>0x1c2</td>
<td>Thumb处理器</td>
</tr>
<tr>
<td>0x169</td>
<td>MIPS小尾WCE v2处理器</td>
</tr>
</table>
<h3><a name="3.2.2"></a>3.2.2 特征</h3>
<p>Characteristics域包含镜像文件属性的标志。以下加粗的是常用的属性。当前定义了以下值(由低位往高位):</p>
<table border="1" cellspacing="1">
<tr>
<td align="center">位置</td>
<td align="center">描述</td>
</tr>
<tr>
<td>0</td>
<td>它表明此文件不包含基址重定位信息,因此必须被加载到其首选基地址上。如果基地址不可用,加载器会报错。</td>
</tr>
<tr>
<td>1</td>
<td>它表明此镜像文件是合法的。看起来有点多此一举,但又不能少。</td>
</tr>
<tr>
<td>2</td>
<td rowspan="3">保留,必须为0。</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
<tr>
<td>5</td>
<td>应用程序可以处理大于2GB的地址。</td>
</tr>
<tr>
<td>6</td>
<td rowspan="2">保留,必须为0。</td>
</tr>
<tr>
<td>7</td>
</tr>
<tr>
<td>8</td>
<td>机器类型基于32位体系结构。</td>
</tr>
<tr>
<td>9</td>
<td>调试信息已经从此镜像文件中移除。</td>
</tr>
<tr>
<td>10</td>
<td>如果此镜像文件在可移动介质上,完全加载它并把它复制到交换文件中。几乎不用</td>
</tr>
<tr>
<td>11</td>
<td>如果此镜像文件在网络介质上,完全加载它并把它复制到交换文件中。几乎不用</td>
</tr>
<tr>
<td>12</td>
<td>此镜像文件是系统文件,而不是用户程序。</td>
</tr>
<tr>
<td>13</td>
<td>此镜像文件是动态链接库(DLL)。</td>
</tr>
<tr>
<td>14</td>
<td>此文件只能运行于单处理器机器上。</td>
</tr>
<tr>
<td>15</td>
<td>保留,必须为0。</td>
</tr>
</table>
<p><b><a name="3.2.3"></a>Windows子系统</b></p>
<p>为NT头第2部分的Subsystem域定义了以下值以确定运行镜像所需的Windows子系统(如果存在):</p>
<table border="1" cellspacing="1">
<tr>
<td align="center">值</td>
<td align="center">描述</td>
</tr>
<tr>
<td>0</td>
<td>未知子系统 </td>
</tr>
<tr>
<td><b>1</b></td>
<td><b>设备驱动程序和Native Windows进程</b></td>
</tr>
<tr>
<td><b>2</b></td>
<td><b>Windows图形用户界面(GUI)子系统(一般程序)</b></td>
</tr>
<tr>
<td><b>3</b></td>
<td><b>Windows字符模式(CUI)子系统(从命令提示符启动的)</b></td>
</tr>
<tr>
<td>7</td>
<td>Posix字符模式子系统</td>
</tr>
<tr>
<td>9</td>
<td>Windows CE</td>
</tr>
<tr>
<td>10</td>
<td>可扩展固件接口(EFI)应用程序</td>
</tr>
<tr>
<td>11</td>
<td>带引导服务的EFI驱动程序</td>
</tr>
<tr>
<td>12</td>
<td>带运行时服务的EFI驱动程序</td>
</tr>
<tr>
<td>13</td>
<td>EFI ROM镜像</td>
</tr>
<tr>
<td>14</td>
<td>XBOX</td>
</tr>
</table>
<p><b><a name="3.2.4"></a>DLL特征</b></p>
<p>为NT头的DllCharacteristics域定义了以下值:</p>
<table border="1" cellspacing="1">
<tr>
<td align="center">位置</td>
<td align="center">描述</td>
</tr>
<tr>
<td>1</td>
<td rowspan="4">保留,必须为0。</td>
</tr>
<tr>
<td>2</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
<tr>
<td>5</td>
<td>官方文档缺失</td>
</tr>
<tr>
<td>6</td>
<td>官方文档缺失</td>
</tr>
<tr>
<td>7</td>
<td>DLL可以在加载时被重定位。</td>
</tr>
<tr>
<td>8</td>
<td>强制进行代码完整性校验。</td>
</tr>
<tr>
<td>9</td>
<td>镜像兼容于NX。</td>
</tr>
<tr>
<td>10</td>
<td>可以隔离,但并不隔离此镜像。</td>
</tr>
<tr>
<td>11</td>
<td>不使用结构化异常(SE)处理。</td>
</tr>
<tr>
<td>12</td>
<td>不绑定镜像。</td>
</tr>
<tr>
<td>13</td>
<td>保留,必须为0。</td>
</tr>
<tr>
<td>14</td>
<td>WDM驱动程序。</td>
</tr>
<tr>
<td>15</td>
<td>官方文档缺失</td>
</tr>
<tr>
<td>16</td>
<td>可以用于终端服务器。</td>
</tr>
</table>
<p>每个数据目录给出了Windows使用的表或字符串的地址和大小。这些数据目录项全部被被加载进内存以备系统运行时使用。数据目录是按照如下格式定义的一个8字节结构:</p>
<p>typedef struct<br>
DWORD VirtualAddress; //数据的RVA<br>
DWORD Size; //数据的大小<br>
typedef ENDS</p>
<p>
第1个域——VirtualAddress,实际上是表的RVA。相对镜像基址偏移地址。NT头第2部分的ImageBase<br>
第2个域给出了表的大小(以字节计)。数据目录组成了NT头的最后一部分。</p>
<p>
<b>Certificate Table域指向属性证书表。它的第一个域是一个文件指针,而不是通常的RVA。</b></p>
<h2><a name="3.3"></a>3.3 节头</h2>
<p>在镜像文件中,每个节的RVA值必须由链接器决定。这样能够保证这些节位置相邻且按升序排列,并且这些RVA值必须是NT头中SectionAlignment域的倍数。</p>
<p>每个节头(节表项)格式如下,共40个字节:</p>
<table border="1" cellspacing="1">
<tr>
<td width="35">偏移</td>
<td width="35">大小</td>
<td align="center">英文名</td>
<td align="center">描述</td>
</tr>
<tr>
<td width="35">0</td>
<td width="35">8</td>
<td>Name</td>
<td>这是一个8字节ASCII编码的字符串,不足8字节时用NULL填充,<b>必须使其达到8字节</b>。如果它正好是8字节,那就没有最后的NULL字符。可执行镜像不支持长度超过8字节的节名。</td>
</tr>
<tr>
<td width="35">8</td>
<td width="35">4</td>
<td>VirtualSize</td>
<td>当加载进内存时这个节的<b>总</b>大小。如果此值比SizeOfRawData大,那么多出的部分用0填充。这是节的数据在没有进行对齐处理前的实际大小,不需要内存对齐。</td>
</tr>
<tr>
<td width="35">12</td>
<td width="35">4</td>
<td>VirtualAddress</td>
<td>内存中节相对于镜像基址的偏移。必须是SectionAlignment的整数倍。</td>
</tr>
<tr>
<td width="35">16</td>
<td width="35">4</td>
<td>SizeOfRawData</td>
<td>磁盘文件中<b>已</b>初始化数据的大小。它必须是NT头中FileAlignment域的倍数。当节中仅包含未初始化的数据时,这个域应该为0。</td>
</tr>
<tr>
<td width="35">20</td>
<td width="35">4</td>
<td>PointerToRawData</td>
<td>节中数据起始的文件偏移。它必须是NT头中FileAlignment域的倍数。当节中仅包含未初始化的数据时,这个域应该为0。</td>
</tr>
<tr>
<td width="35">24</td>
<td width="35">4</td>
<td>PointerToRelocations</td>
<td>重定位项开头的文件指针。对于可执行文件或没有重定位项的文件来说,此值应该为0。</td>
</tr>
<tr>
<td width="35">28</td>
<td width="35">4</td>
<td colspan="2">已经废除。</td>
</tr>
<tr>
<td width="35">32</td>
<td width="35">2</td>
<td>NumberOfRelocations</td>
<td>节中重定位项的个数。对于可执行文件或没有重定位项的文件来说,此值应该为0。</td>
</tr>
<tr>
<td width="35">34</td>
<td width="35">2</td>
<td colspan="2">已经废除。</td>
</tr>
<tr>
<td width="35">36</td>
<td width="35">4</td>
<td>Characteristics</td>
<td>描述节特征的标志。参考“<a href="#3.3.1">节标志</a>”。</td>
</tr>
</table>
<h2><a name="3.3.1"></a>3.3.1 节标志</h2>
<p>节头中的Characteristics标志指出了节的属性。(以下加粗的是常用的属性值)</p>
<table border="1" cellspacing="1">
<tr>
<td>位置</td>
<td>描述</td>
</tr>
<tr>
<td>1</td>
<td rowspan="5">已经废除</td>
</tr>
<tr>
<td>2</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
<tr>
<td>5</td>
</tr>
<tr>
<td><b>6</b></td>
<td><b>此节包含可执行代码。代码段才用“.text”</b></td>
</tr>
<tr>
<td><b>7</b></td>
<td><b>此节包含已初始化的数据。“.data”</b></td>
</tr>
<tr>
<td>8</td>
<td>此节包含未初始化的数据。“.bss”</td>
</tr>
<tr>
<td>9</td>
<td rowspan="7">已经废除</td>
</tr>
<tr>
<td>10</td>
</tr>
<tr>
<td>11</td>
</tr>
<tr>
<td>12</td>
</tr>
<tr>
<td>13</td>
</tr>
<tr>
<td>14</td>
</tr>
<tr>
<td>15</td>
</tr>
<tr>
<td>16</td>
<td>此节包含通过全局指针(GP)来引用的数据。</td>
</tr>
<tr>
<td>17</td>
<td rowspan="8">已经废除</td>
</tr>
<tr>
<td>18</td>
</tr>
<tr>
<td>19</td>
</tr>
<tr>
<td>20</td>
</tr>
<tr>
<td>21</td>
</tr>
<tr>
<td>22</td>
</tr>
<tr>
<td>23</td>
</tr>
<tr>
<td>24</td>
</tr>
<tr>
<td>25</td>
<td>此节包含扩展的重定位信息。</td>
</tr>
<tr>
<td>26</td>
<td>此节可以在需要时被丢弃。</td>
</tr>
<tr>
<td>27</td>
<td>此节不能被缓存。</td>
</tr>
<tr>
<td>28</td>
<td>此节不能被交换到页面文件中。</td>
</tr>
<tr>
<td>29</td>
<td>此节可以在内存中共享。</td>
</tr>
<tr>
<td><b>30</b></td>
<td><b>此节可以作为代码执行。</b></td>
</tr>
<tr>
<td><b>31</b></td>
<td><b>此节可读。(几乎都设置此节)</b></td>
</tr>
<tr>
<td><b>32</b></td>
<td><b>此节可写。</b></td>
</tr>
</table>
<p>第25标志表明节中重定位项的个数超出了节头中为每个节保留的16位所能表示的范围(也就是65535个函数)。如果设置了此标志并且节头中的NumberOfRelocations域的值是0xffff,那么实际的重定位项个数被保存在第一个重定位项的VirtualAddress域(32位)中。如果设置了第25标志但节中的重定位项的个数少于0xffff,则表示出现了错误。</p>
<h1><a name="4"></a>4 一些注意信息</h1>
<p><b>1.PE头是怎么计算的?</b></p>
<p>
SizeOfHeaders所指的头是从文件的第1个字节开始算起的,而不是从PE标记开始算起的。快速的计算方法是从文件的偏移0x3C(第59字节)处获得一个4字节的PE文件签名的偏移地址,这个偏移地址就是本文所定义的DOS头的大小。NT头在32位系统是244字节,在64位系统是260字节。节头的大小由NT头的第1部分的NumberOfSections(节的数量)*40字节(每个节头是40字节)得出。如此,DOS头、NT头、节头3个头的大小加起来并向上舍入为FileAlignment(文件对齐)的正整数倍的最小值就是SizeOfHeaders(头大小)值。</p>
<p>
<b>2.节数量的问题</b></p>
<p>
Windows读取NumberOfSections的值然后检查节表里的每个结构,如果找到一个全0结构就结束搜索,否则一直处理完NumberOfSections指定数目的结构。没有规定节头必须以全0结构结束。所以加载器使用了双重标准——全0、达到NumberOfSections数量就不再搜索了。</p>
<p>
<b>3.未初始化问题</b></p>
<p>
①未初始化数据在文件中是不占空间的,但在内存里还是会占空间的,它们依然依据指定的大小存在内存里。所以说未初始化数据只在文件大小上有优势,在内存里与已初始化数据是一样的。<br>
②未初始化数据的方法有2种:1是通过节头的VirtualSize>SizeOfRawData。未初始化数据的大小就是VirtualSize-SizeOfRawData的值。2是节特征的标志置为“<b>此节包含未初始化的数据</b>”,这时SizeOfUninitializedData才会非0。现在
都使用第1种,把它们集成到.data里面可以加快速度。</p>
<p>
<b>4.已初始化问题</b></p>
<p>
数据目录里面所对应的块中除了<b>属性证书表、调试信息和几个废除的目录项</b>外,全都属于SizeOfInitializedData(已初始化数据大小)范围。当然,已初始化数据不只这些,还可以是常见的代码段等等。</p>
<p>
<b>5.节对齐的问题</b></p>
<p>
如果NT头的SectionAlignment域的值小于相应<b>操作系统</b>(有些资料说是根据CPU来的,这不一定。因为CPU本身就允许改分页大小,只是大部分时候操作系统是用CPU默认值的。x86平台默认页面大小是4K。IA-64平台默认页面大小是8K。MIPS平台默认页面大小是4K。Itanium平台默认页面大小是8K。)平台的页面大小,那么镜像文件有一些附加的限制。对于这种文件,当镜像被加载到内存中时,节中数据在文件中的位置必须与它在内存中的位置相等,因此节中数据的物理偏移与RVA相同。</p>
<p>
<b>6.镜像大小</b></p>
<p>
SizeOfImage所代表的<b>内存镜像大小没有包含属性证书表和调试信息</b>,这是因为加载器并不将属性证书和调试信息映射进内存。同时加载器规定,属性证书和调试信息必须被放在镜像文件的最后,并且属性证书表在调试信息节之前。</p>
<p>
<b>7.数据的组织</b></p>
<p>
CPU的段主要分为4个:代码段、数据段、堆栈段、附加段。而操作系统给程序员留下只有代码段和数据段,堆栈段和附加段就由系统自行处理了,我们不用管。PE文件的数据组织方式是以BaseOfCode、BaseOfData为基准,以节为主体,以数据目录为辅助。<br>
①BaseOfCode、BaseOfData是与后面相应的代码节、数据节的VirtualAddress一致。(这里的数据节是狭义的数据节,是特指代码段、数据目录所指定的数据除外的那一部分,也就是我们编程时定义的常量、变量、未初始化数据等)<br>
②所有的代码、数据都必须在节里面,否则就算是代码基址、数据基址、数据目录都有指定,而节头里没有指定,加载器也会报错,不能运行<br>
③导入函数、导出函数、资源、重定位表等是为了辅助程序主体的,这些都由系统负责处理</p>
<h1><a name="5"></a>5 特殊的节</h1>
<p>下表描述了保留的节以及它们的属性,后面是对出现在可执行文件中的节的详细描述。这些节是微软的编译产品所定义的不是系统定义的,实际可以不拘泥于此。</p>
<table border="1" cellspacing="1">
<tr>
<td>节名</td>
<td>内容</td>
</tr>
<tr>
<td>.bss</td>
<td>未初始化的数据</td>
</tr>
<tr>
<td><b>.data</b></td>
<td><b>代码节</b></td>
</tr>
<tr>
<td><b>.edata</b></td>
<td><b>导出表</b></td>
</tr>
<tr>
<td><b>.idata</b></td>
<td><b>导入表</b></td>
</tr>
<tr>
<td>.idlsym</td>
<td>包含已注册的SEH,它们用以支持IDL属性</td>
</tr>
<tr>
<td>.pdata</td>
<td>异常信息</td>
</tr>
<tr>
<td><b>.rdata</b></td>
<td><b>只读的已初始化数据(用于常量)</b></td>
</tr>
<tr>
<td><b>.reloc</b></td>
<td><b>重定位信息</b></td>