PE文件是Windows操作系统下可使用的可执行文件格式。PE文件是指32位的可执行文件,也称为PE32。64位的可执行文件称为PE+或PE32+。是PE(PE32)文件的一种扩展形式(而非PE64)。

PE文件格式

PE文件种类如表
C(2O$`~OAKI[QBCLMSXOGK5.png](https://cdn.nlark.com/yuque/0/2020/png/554486/1582278794191-41f840a5-4647-45ff-a6c1-4ff12ce51fad.png#align=left&display=inline&height=121&name=C%282O%24%60~OAKI%5BQBCLMSXOGK5.png&originHeight=121&originWidth=746&size=50310&status=done&style=none&width=746)<br />严格地说,OBJ(对象)文件之外的所有文件都是可执行的。DLL,SYS文件等虽然不能直接在Shell(Explorer.exe)中运行,但可以使用其他方法(调试器、服务等)执行。<br />提示:根据PE正式规范,编译结果OBJ文件也视为PE文件。但是OBJ文件本身不能以任何形式执行,在代码逆向分析中几乎不需要关注。<br />以记事本(notepad.exe)程序进行简单说明,首先使用Hex Editor打开记事本程序。<br />图示处为notepad.exe文件的起始部分,也是PE文件的头部分(PE header)。notepad.exe文件运行需要的所有信息就存储在这个PE头中。如何加载到内存、从何处开始运行、运行中需要的DLL有哪些、需要多大的栈/堆内容等,大量信息以结构体形式存储在PE头中。换言之,学习PE文件格式就是学习PE头中的结构体。<br />提示:书中将以Windows XP SP3的notepad.exe为例进行说明,与其他版本Windows下的notepad.exe文件结构类似,但是地址不同。<br />![TQ}MGO`~THKZD{C%4]3%]ZM.png

基本结构

notepad,exe具有普通PE文件的基本结构。图13-2描述了notepad.exe文件加载到内存时的情形。其中包含了许多内容。
![OVZJV$]GZUJFO8%J]VMBCV.png
从DOS头(DOS header)到节区头(Section header)是PE头部分,其下的节区合称PE体。文件中使用偏移(offest),内存中使用VA(Virtual Address,虚拟地址)来表示位置。文件加载到内存时,情况就会发生变化(节区的大小、位置等)。文件的内容一般可分为代码(.text)、数据(.data)、资源(.rsrc)节,分别保存。
提示:根据所用的不同的开发工具与编译选项,节区的名称、大小、个数、存储的内容等都是不同的。最重要的是它们按照不同的用途分类保存到不同的节中。
各节区头定义了各节区在文件或内存中的大小、位置、属性等。
PE头与各节区的尾部存在一个区域,称为NULL填充(NULL padding)。计算机中,为了提高处理文件、内存、网络包的效率,使用“最小基本单位”这一概念,PE文件中也类似。文件/内存中节区的起始位置应该在各文件/内存最小单位的倍数位置上,空白区域将用NULL填充。

VA&RVA

VA指的是进程虚拟内存的绝对地址,RVA(Relative Virtual Address,相对虚拟地址)指从某个基准位置(ImageBase)开始的相对地址。VA与RVA满足下面的换算关系。
RVA+ImageBase=VA
PE头内部信息大多以RVA形式存在。原因在于,PE文件(主要是DLL)加载到进程虚拟内存的特定位置时,该位置可能已经加载了其他PE文件(DLL)。此时必须通过重定位(Relocation)将其加载到其他空白的位置,若PE头信息使用的是VA,则无法正常访问。因此使用RVA来定位信息,即是发生了重定位,只要相对于基准位置的相对地址没有变化,就能正常访问到指定信息。

PE头

PE头由许多结构体组成。

DOS头

微软创建PE文件格式时,人们正广泛使用DOS文件,所以微软充分考虑了PE文件对DOS文件的兼容性。其结果是在PE头的最前面添加了一个IMAGE_DOS_HEADER结构体,用来扩展已有的DOS EXE头。
]`OY)@LI30PTNGR9W0%E$NY.png
IMAGE_DOS_HEADER结构体的大小为40个字节。在该结构体中必须知道2个重要成员:e_magic与e_lfanew。
e_magic:DOS签名(signature,4D5A=>ASCII值“MZ”)。
e_lfanew:指示NT头的偏移(根据不同文件拥有可变值)。
所有PE文件在开始部分(e_magic)都有DOS签名(“MZ”)。e_lfanew值指向NT头所在位置(NT头的名称为IMAGE_DOS_HEADERS)。
使用HexEditor打开notepad.exe,查看IMAGE_DOS_HEADERS结构体。
%JB2GWYJ)M`L`03_N3JGR@P.png
根据PE规范,文件开始的2个字节为4D5A,e_lfanew值为000000E0(不是E0000000)。
这些值修改后程序无法正常运行,因为根据PE规范,它已不再是PE文件了。

DOS存根

DOS存根(sub)在DOS头下方,是个可选项,且大小不固定(即是没有DOS存根,文件也能正常运行)。
DOS存根由代码与数据混合而成,图示即为notepad.exe的DOS存根。
ZLISE183$C2E{RU1LNXUB(B.png
文件偏移40-4D区域为16位的汇编指令。32位的Windows OS中不会运行该命令(由于被识别为PE文件,所
以完全忽视该代码)。在DOS环境中运行Notepad.exe文件,或者使用DOS调试器(debug)运行它,可使其执行该代码(不认识PE文件格式,所以被识别为DOS EXE文件)。
打开命令行窗口(cmd.exe),输入如下命令(仅适用于Windows XP环境)。
debug C:\Windows\notepad.exe
在出现的光标位置上输入“u”指令(Unassemble),将会出现16位的汇编指令,如下所示。
RFXN7CGE3_M(0%6K4Z5XWPH.png
代码非常简单,在画面中输出字符串“This program cannont be run in DOS mede”后就退出。换言之,
notepad.exe文件虽然是32位的PE文件,但是带有MS-DOS兼容模式,可以在DOS环境中运行,执行DOS EXE代码,输出“This program cannont be run in DOS mede”后终止。灵活使用该特性可以在一个可执行文件中创建出另一个文件,它在DOS与Windows中都能运行(在DOS环境中运行16位DOS代码,在Windows环境中运行32位Windows代码)。

NT头

NT头IMAGE_NT_HEADERS。
O0357QM{1F(FA]K)MH9(OGO.png
IMAGE_NT_HEADERS结构体由3个成员组成,第一个成员为签名(signature)结构体,其值为50450000h(“PE”00)。另外两个成员分别为文件头(File Header)与可选头(Optional Header)结构体。使用Hex Editor打开notepad.exe,查看其IMAGE_NT_HEADERS。
96FOG9~7M[7Z$5]PHFA)J(I.png

NT头:文件头

文件头是表现文件大致属性的IMAGE_FILE_HEADERS结构体。
H)CGUVN9Q@6Q}D[WLQ6KT}1.png](https://cdn.nlark.com/yuque/0/2020/png/554486/1582455072162-55aff22a-5e53-4ce4-9d7d-577a5c1bd617.png#align=left&display=inline&height=179&name=H%29CGUVN9Q%406Q%7DD%5BWLQ6KT%7D1.png&originHeight=179&originWidth=737&size=139470&status=done&style=none&width=737)<br />IMAGE_FILE_HEADERS结构体中有如下4种重要成员(若它们设置不正确将导致文件无法正常运行)。<br />**#1、Machine**<br />每个CPU都拥有唯一的Machine码,兼容32位Intel x86芯片的Machine码为14C。以下是定义在winnt.h文件中的Machine码。<br />![@SA@[``CGD2$K6LNE9NTK06.png](https://cdn.nlark.com/yuque/0/2020/png/554486/1582455346600-2fe5f40b-dfbc-421a-b1fd-3a84b2f7177d.png#align=left&display=inline&height=127&name=%40SA%40%5B%60%60CGD2%24K6LNE9NTK06.png&originHeight=127&originWidth=736&size=108329&status=done&style=none&width=736)<br />![RMNABX@EVMZR[[C71]OSI}9.png
#2、NumberOfSections
前面提到过,PE文件把代码、数据、资源等依据属性分类到各节区中存储。
NumberOfSections用来指出文件中存在的节区数量。该值一定要大于0,且当定义的节区数量与实际节区不同时,将发生运行错误。
#3、SizeOfOptionalHeader
IMAGE_NT_HEADER结构体的租后一个成员为IMAGE_OPTIONAL_HEADER32结构体。SizeOfOptionalHeader成员用来指出IMAGE_OPTIONAL_HEADER32结构体的长度。IMAGE_OPTIONAL_HEADER32结构体由C语言编写而成,故其大小已经确定。但是Windows的PE装载器需要查看IMAGE_FILE_HEADERS的SizeOfOptionalHeader值,从而识别出IMAGE_OPTIONAL_HEADER32结构体的大小。
PE32+格式的文件中使用的是IMAGE_OPTIONAL_HEADER64结构体,而不是IMAGE_OPTIONAL_HEADER32结构体。2个结构体的尺寸是不同的,所以需要在SizeOfOptionalHeader成员中明确指出结构体的大小。
提示:借助IMAGE_DOS_HEADER的e_lfanew成员与IMAGE_FILE_HEADERS的SizeOfOptionalHeader成员,可以创建出一种脱离常规的PE文件(PE Patch)(也有人称之为“麻花”PE文件中)。
#4、Characteristics
该字段用于标识文件的属性,文件是否可运行的形态,是否为DLL文件等信息,以bit OR形式组合起来。
以下是定义在winnt.h文件中的Characteristics值(请记住0002h与2000h这两个值)。
FE217PFWSO54}R@QDR3~H0V.png
)[DKRT6S7LEPKL]3D09KG{M.png
另外,PE文件中Characteristics的值有可能不是002h吗(不可执行)?是的,确实存在这种情况。比如类似.obj的objecct文件及resource DLL文件等。
最后讲一下IMAGE_FILE_HEADER的TimeDateStamp成员。该成员的值不影响文件运行,用来记录编译器创建此文件的时间。但是有些开发工具提供了设置该值的工具,而有些开发工具(Delphi)则未提供(且随所选用选项的不同而不同)。
*IMAGE_FILE_HEADER

在Hex Editor中查看notepad.exe的IMAGE_FILE_HEADER结构体。
LKD`@}{@}17L3{W006BASS0.png
为使大家理解图13-6,以结构体成员形式表示如下。
$Y@WNZ~U6~1AAS@9QW1KGGM.png

NT头:可选头

IMAGE_OPTIONAL_HEADER32是PE头结构体中最大的。
VIPEGXZSHBOBV2%XLB56(6H.png
XMR]I{CFQ2KHVHN2(`1Z]HE.png
在IMAGE_OPTIONAL_HEADER32结构体中需要关注下列成员。这些值是文件运行必需的,设置错误将导致文件无法正常运行。
#1、Magic
为IMAGE_OPTIONAL_HEADER32结构体时,Magic码为10B;为IMAGE_OPTIONAL_HEADER64结构体时为20B。
#2、AddressOfEntryPoint
AddressOfEntryPoint持有EP的RVA值。该值指出程序最先执行的代码起始地址,相当重要。
#3、ImageBase
进程虚拟内存的范围是0~FFFFFFFF(32位系统)。PE文件被加载到如此大的内存时,ImageBase指出文件的优先装入地址。
EXE、DLL文件被装载到用户内存的0~7FFFFFFF中,SYS文件被载入内核内存的80000000~FFFFFFFF中。一般而言,使用开发工具创建好EXE文件后,其ImageBase的值为00400000,DLL文件的ImageBase值为10000000(当然也可以指定为其他值)。执行PE文件时,PE装载器先创建进程,再将文件载入内存,然后把EIP寄存器的值设置为ImageBase+AddressOfEnteyPoint。
#4、SectionAlignment,FileAlignment
PE文件的Body部分划分为若干节区,这些节区存储着不同类别的数据。FileAlignment指定了节区在磁盘文件中的最小单位,而SectionAlignment则指定了节区在内存中的最小单位(一个文件中,FileAlignment与SectionAlignment值可能相同,也可能不同)。磁盘文件或内存的节区大小必定为FileAlignment或SectionAlignment值的整数倍。
#5、SizeOfImage
加载PE文件到内存时,SizeOfImage指定了PE Image在虚拟内存中所占空间的大小。一般而言文件的大小与加载到内存中的大小是不同的(节区头中定义了个节装载的位置与占有内存的大小)。
#6、SizeOfHeader
SizeOfHeader用来指出整个PE头的大小。该值也必须是FileAlignment的整倍数。第一节区所在位置与SizeOfHeader距文件开始偏移的量相同。
#7、Subsystem
该Subsystem值用来区分系统驱动文件(.sys)与普通的可执行文件(.exe,.dll)。Subsystem成员可拥有的值如表所示。
![7EGO{8]QBIC404BO~ZQ]TI.png
#8、NumberOfRvaAndSizes
NumberOfRvaAndSizes用来指定DataDirectory(IMAGE_OPTIONAL_HEADER32结构体的最后一个成员)数组的个数。虽然结构体定义中明确指出数组个数为IMAGE_NUMBEROF_DIRECTORY_ENTRIES(16),但是PE装载器通过查看NumberOfRvaAndSizes值来识别数组大小,换言之,数组大小也可以不是16。
#9、DataDirectory
DataDirectory是由IMAGE_DATA_DIRECTORY结构体组成的数组,数组的每项都有被定义的值。
SR@J{TE9E9}X~O14T{8XJG5.png
将此处所说的Directory香橙某个结构体数组即可。希望各位重点关注标红的EXPORT/IMPORT/RESOURCE、TLS Direction。特别需要注意的是IMPORT与EXPORT Directory,它们是PE头中非常重要的部分,其余部分不怎么重要,大致了解。

IMAGE_OPTIONAL_HEADER

查看notepad.exe的IMAGEOPTIONAL_HEADER整个结构体。
8~M~VU9HSA$EHDZ{0{$P{(1.png
图中,Hex Editor(HxD)描述的是notepad.exe的IMAGE_OPTIONAL_HEADER结构体区域。结构体各成员的值及其说明如图所示。
![44)OU@XE3
@VST%IQEFEM~H.png](https://cdn.nlark.com/yuque/0/2020/png/554486/1582812657821-989cfbc4-0248-41de-9861-c4fc9c92a948.png#align=left&display=inline&height=675&name=44%29OU%40XE3_%40VST%25IQEFEM~H.png&originHeight=675&originWidth=732&size=488551&status=done&style=none&width=732)
])M{)(U28]~QW$QW_CEGIX7.png

节区头

节区头中定义了各节区属性,PE文件中的code(代码)、data(数据)、resource(资源)等按照属性分类存储在不同节区。
我认为把PE文件创建成多个节区结构的好处是,这样可以保证程序的安全性。若把code与data放在一个节区中相互纠缠(实际上完全可以这么做)很容易引发安全问题。
假如向字符串data写数据时,由于某个原因导致溢出(输入超过缓冲区大小时),那么其下的code(指令)就会被覆盖,应用程序就会崩溃。因此,PE文件格式设计时决定把具有相似属性的数据统一保存在一个被称为节区的地方,然后需要把各节区属性记录在节区头中(节区属性中有文件/内存的起始地址、大小、访问权限等)。
换言之,需要为每个code/data/resource分别设置不同的特性、访问权限等,如表所示。
9U6M]VK}`FS%H8U4RM18X}U.png

IMAGE_SECTION_HEADER

节区头是由IMAGE_SECTION_HEADER结构体组成的数组,每个结构体对应一个节区。
%)UU(~4N8(8GO)DJKNM%VHJ.png
表中列出了IMAGE_SECTION_HEADER结构体中要了解的重要成员(不使用其他成员)。
6)P7FEJ{_F_K[(WO1S]X)]E.png
VirtualAddress与PointerToRawData不带有任何值,分别由(定义在IMAGE_SECTION_HEADER32中的)
SectionAlignment与FileAlignment确定。
VirtualSize与SizeOfRawData一般具有不同的值,即磁盘文件中节区的大小与加载到内存中的节区大小是不同的。
Characterisitics由代码13-10中显示的值组合(bit OR)而成。
AAPDYJV0MZ{CBQS1ZI(5TGI.png
关于Name字段。Name成员不像C语言中的字符串一样以NULL结束,并且没有“必须使用ASCII值”的限制。PE规范未明确规定节区的Name,所以可以向其中放入任何值,甚至可以填充NULL。所以节区的Name仅供参考,不能保证其百分之百地被用作某种信息(数据节区的名称也可叫做.code)。
notepad.exe的节区头数组(共有3个节区)。如图所示。
![C{OX3XJ039EXRB`P2ZOUI2.png
各结构体成员如代码所示。
![I~%X9$DI{54TP9{8VJ%XHP.png
提示:PE文件加载到内存时,文件不会原封不动地加载,而要根据节区头中定义的节区起始地址、节区大小等加载。因此,磁盘文件中的PE与内存中的PE具有不同的形态。将装载到内存中的形态称为“映像(Image)”以示区别。

RAV to RAW

有关PE文件从磁盘到内存映射的内容。PE文件加载到内存时,每个节区都要能准确完成内存地址与文件偏移间的映射。这种映射一般称为RVA to RAW,方法如下。
(1)查找RVA所在节区。
(2)使用简单的公式计算文件偏移(RAW)。
根据IMAGE_SECTION_HEADER结构体,换算公式如下:
[2T)ZRMG2E)G($}U3ST`{KF.png](https://cdn.nlark.com/yuque/0/2020/png/554486/1582883452587-3462910c-bfc3-46b7-be91-98127a55e4ca.png#align=left&display=inline&height=40&name=%5B2T%29ZRMG2E%29G%28%24%7DU3ST%60%7BKF.png&originHeight=40&originWidth=597&size=22474&status=done&style=none&width=597)<br />RVA与RWA的关系<br />原理比较简单:首先判断这个地址是否在PE头中,如果在,文件偏移和内存偏移相等,如果存在于文件的区段中,则利用以下公式:<br />内存偏移 - 该段起始的RVA(VirtualAddress) = 文件偏移 - 该段的PointerToRawData内存偏移 = 该段起始的RVA(VirtualAddress) + (文件偏移 - 该段的PointerToRawData)文件偏移 = 该段的PointerToRawData + (内存偏移 - 该段起始的RVA(VirtualAddress))<br />**Quiz**<br />分别计算各个RVA。<br />![6$K]DSW_Z1WSB1T5TNOM6V4.png
`@M`]Z]D4S%NW6N0YC]UC}3.png

IAT

IAT(Import Address Table,导入地址表)保存的内容与Windows操作系统的核心进程、内容、DLL结构有关。换句话说,只要理解了IAT,就掌握了Windows操作系统的根基。换言之,IAT是一种表格,用来记录程序正在使用哪些库中的哪些函数。

DLL

DLL(Dynamic Linked Library)中文为“动态链接库”,16位的DOS时代不存在DLL这一概念,只有“库”(Library)一说。比如在C语言中使用printf()时,编译器会先从C库中读取相应函数的二进制代码,然后插入(包含到)应用程序。也就是说,可执行文件中包含着printf()函数的二进制代码。Windows OS支持多任务,若仍采用这种包含库的方式,会非常没有效率。Windows操作系统使用了数量庞大的库函数(进程、内存、窗口、消息等)来支持32位的Windows环境。同时运行多个程序时,若仍像以前一样每个程序运行时都包含相同的库,将造成严重的内存浪费(磁盘空间的浪费也不容小觑)。因此,Windows OS设计者们根据需要引入了DLL这一概念,描述如下:
#不要把库包含到程序中,单独组成DLL文件,需要时调用即可。
#内存映射技术使加载后的DLL代码、资源在多个进程中实现共享。
#更新库时只要替换相关DLL文件即可,简便易行。
加载DLL的方式实际有两种:一种是“显示连接”(Explicit Linking),程序使用DLL时加载使用完毕后释放内存;另一种是“隐式连接”(Implicit Linking),程序开始时即一同加载DLL,程序终止时再释放占用的内存。IAT提供的机制即与隐式链接有关。下面使用OD打开notepad.exe来查看IAT。图中是调用CreateFileW()函数的代码,该函数位于kernel32.dll中。
~}7II(T$}XIW{DL5P{N_W3J.png
调用CreateFileW()函数时并非直接调用,而是通过获取01001104地址处的值来实现(所有API调用均采用这种方式)。
地址01001104是notepad.exe中.text节区的内存区域(更确切地说是IAT内存区域)。01001104地址的值为7C8107F0,而该地址即是加载到notepad.exe进程内存中的CreateFileW()函数(位于kernel32.dll库中)的地址。此处产生一个疑问。
“直接使用CALL 7C8107F0指令调用函数不是更准确、更好吗?”
事实上,notepad.exe程序的制作者编译(生成)程序时,并不知道该notepad.exe程序要运行在哪个版本
的Windows、哪种语言、哪种服务包下(Service Pack)。上面列举的所有环境中,kernel32.dll的版本各不相同,CreateFileW()函数的位置(地址)也不相同。为了确保在所有环境中都能正常调用CreateFileW()函数,编译器准备了要保存CreateFileW()函数实际地址的位置(01001104),并仅记下CALL DWORD PTR DS:[1004404]形式的指令。执行文件时,PE装载器将CreateFileW()函数的地址写到01001104位置。
编译器不使用CALL 7C8107F0语句的另一个原因在于DLL重定位。DLL文件的ImageBase值一般为10000000.比如某个程序使用a.dll与b.dll时,PE装载器先把a.dll装载到内存的10000000(ImageBase)处,然后尝试把b.dll也装载到该处。但是由于改地址处已经装载了a.dll,所以PE装载器查找其他空白的内存空间(ex:3E000000),然偶后将b.dll装载进去。
这就是所谓的DLL重定位,它使我们无法对实际地址硬编码。另一个原因在于,PE头中表示地址时不使用VA,而是RVA。
提示:实际操作中无法保证DLL一定会被加载到PE头内指定的ImageBase处。但是EXE文件(生成进程的主体)
却能准确加载到自身的ImageBase中,因为他拥有自己的虚拟空间。

IMAGE_IMPORT_DESCRIPTOR

IMAGE_IMPORT_DESCRIPTOR结构体中记录着PE文件要导入哪些库文件。
提示: Import:导入,向库提供服务(函数)。
Export:导出,从库中向其它PE文件提供服务(函数)。
IMAGE_IMPORT_DESCRIPTOR结构体如图所示。
SYP6V)B_GES}%[1}2YP9]FX.png执行一个普通程序时往往需要导入多个库,导入多少库就存在多少个IMAGE_IMPORT_DESCRIPTOR结构体,这
些结构体形成了数组,且结构体数组最后以NULL结构体结束。IMAGE_IMPORT_DESCRIPTOR中的重要成员如表所示(拥有全部RVA值)。
I01XD3SZ9OU4RN87S@TP44W.png
提示:#PE头中的“Table”即指数组。
#INT与IAT是长整型数组,以NULL结束。
#INT中各元素的值为IMAGE_IMPORT_BY_NAME结构体指针(有时IAT也拥有相同的值)。
#INT与IAT的大小应相同。
图中描述了notepad.exe之kernel32.dll的IMAGE_IMPORT_DESCRIPTOR结构。
AZIUXR~ES4P86)RX~)73BDP.png
图中,INT与IAT的各元素同时指向相同地址,但也有很多情况下它们是不一致的。
PE装载器把导入函数输入至IAT的顺序。
![H(YZ0QJQTKG825V9~D@R$M.png

EAT

Windows操作系统中,“库”是为了方便其他程序调用而集中包含相关函数的文件(DLL/SYS)。Win32 API是最具代表性的库,其中的kernel32.dll文件被称为最核心的库文件。
EAT是一种核心机制,它使不同的应用程序可以调用库文件中提供的函数。也就是说,只有通过EAT才能准确求得从相应库中导出函数的起始地址。与前面讲解的IAT一样,PE文件内的特定结构体IMAGE_EXPORT_DESCRIPTORY
保存着导出的信息,且PE文件中仅有一个用来说明库EAT的IMAGE_EXPORT_DESCRIPTORY结构体。
提示:用来说明IAT的IMAGE_EXPORT_DESCRIPTORY结构体MAGE_EXPORT_DESCRIPTORY结构体以数组形式存在,且拥有多个成员。因为PE文件可以同时导入多个库。
可以在PE文件的PE头中查找到IMAGE_EXPORT_DIRECTORY结构体的位置,IMAGE_OPTIONAL_HEADER32.Data
Directory[0].VirtualAddress值即是IMAGE_EXPORT_DIRECTORY结构体数组的起始位置(也是RVA的值)。
图中显示的是kernel32.dll文件的IMAGE_OPTIONAL_HEADER32.DataDirectory[0](第一个4字节为VirtualAddress,第二个4字节为Size成员)。
M6]T]P1A]R0KPZMU04C{`SH.png
为了便于查看,将图13-20中的IMAGE_OPTIONAL_HEADER32.DataDirectory结构体数组信息整理如下表13-8(深色部分为“导出”相关信息)。
8])_5X)~7L)Q]`RPTC@16@8.png
由于RVA值为262C,所以文件偏移为1A2C。