深入剖析PE文件

来源:互联网 发布:图片特效制作软件 编辑:程序博客网 时间:2024/05/16 07:49

不赖猴的笔记,转载请注明出处。  

深入剖析PE文件

PE文件是Win32的原生文件格式.每一个Win32可执行文件都遵循PE文件格式.PE文件格式的了解可以加深你对Win32系统的深入理解.

 

一、        基本结构。

上图便是PE文件的基本结构。(注意:DOS MZ Header和部分PE header的大小是不变的;DOS stub部分的大小是可变的。)

一个PE文件至少需要两个Section,一个是存放代码,一个存放数据。NT上的PE文件基本上有9个预定义的Section。分别是:.text, .bss, .rdata, .data, .rsrc, .edata, .idata, .pdata, .debug。一些PE文件中只需要其中的一部分Section.以下是通常的分类:

l         执行代码Section , 通常命名为: .text (MS) or CODE (Borland)

l         数据Section, 通常命名为:.data, .rdata, .bss(MS) DATA(Borland).

l         资源Section, 通常命名为:.edata

l         输入数据Section, 通常命名为:.idata

l         调试信息Section,通常命名为:.debug

这些只是命名方式,便于识别。通常与系统并无直接关系。通常,一个PE文件在磁盘上的映像跟内存中的基本一致。但并不是完全的拷贝。Windows加载器会决定加载哪些部分,哪些部分不需要加载。而且由于磁盘对齐与内存对齐的不一致,加载到内存的PE文件与磁盘上的PE文件各个部分的分布都会有差异。

当一个PE文件被加载到内存后,便是我们常说的模块(Module),其起始地址就是所谓的HModule.

 

二、        DOS头结构。

所有的PE文件都是以一个64字节的DOS头开始。这个DOS头只是为了兼容早期的DOS操作系统。这里不做详细讲解。只需要了解一下其中几个有用的数据。

1.       e_magicDOS头的标识,为4Dh5Ah。分别为字母MZ

2.       e_lfanew:一个双字数据,为PE头的离文件头部的偏移量。Windows加载器通过它可以跳过DOS Stub部分直接找到PE头。

3.       DOS头后跟一个DOS Stub数据,是链接器链接执行文件的时候加入的部分数据,一般是“This program must be run under Microsoft Windows”。这个可以通过修改链接器的设置来修改成自己定义的数据。

 

三、        PE头结构。

PE头的数据结构被定义为IMAGE_NT_HEADERS。包含三部分:

 

1.       SignaturePE头的标识。双字结构。为50h, 45h, 00h, 00h. 即“PE”。

2.       FileHeader20字节的数据。包含了文件的物理层信息及文件属性。

这里主要注意三项。

l         NumberOfSections:定义PE文件Section的个数。如果对PE文件新增或删除Section的话,一定要记的修改此域。

l         SizeOfOptionalHeader:定义OptionHeader结构的大小。

l         Characteristics:主要用来标识当前的PE文件是执行文件还是DLL。其各位都有具体的含义。

数据位

Windows.inc的预定义

为1时的含义

0

IMAGE_FILE_RELOCS_STRIPPED

文件中不存在重定位信息

1

IMAGE_FILE_EXECUTABLE_IMAGE

文件是可执行的

2

IMAGE_FILE_LINE_NUMS_STRIPPED

不存在行信息

3

IMAGE_FILE_LOCAL_SYMS_STRIPPED

不存在符号信息

7

IMAGE_FILE_BYTES_REVERSED_LO

小尾方式

8

IMAGE_FILE_32BIT_MACHINE

只在32位平台运行

9

IMAGE_FILE_DEBUG_STRIPPED

不包含调试信息

10

IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP

不能从可移动盘运行

11

IMAGE_FILE_NET_RUN_FROM_SWAP

不能从网络运行

12

IMAGE_FILE_SYSTEM

系统文件。不能直接运行

13

IMAGE_FILE_DLL

DLL文件

14

IMAGE_FILE_UP_SYSTEM_ONLY

文件不能在多处理器上运行

15

IMAGE_FILE_BYTES_REVERSED_HI

大尾方式

 

3.       OptionalHeader:总共224个字节。最后128个字节为数据目录(Data Directory)

以下是字段的说明:

l         AddressOfEntryPoint:程序入口点地址。但加载器要运行加载的PE文件时要执行的第一个指令的地址。它是一个RVA(相对虚拟地址)地址。一些对PE文件插入代码的程序就是修改此处的地址为要运行的代码,然后再跳转回此处原来的地址。

l         ImageBasePE文件被加载到内存的期望的基地址。对于EXE文件,通常加载后的地址就期望的地址。但是DLL却可能是其他的。因为如果这个地址被占,系统就会重新分配一块新的内存,同时会修改此处加载后的地址。EXE文件通常是400000h.

l         SectionAlignment每一个Section的内存对齐粒度。比如:此值为4096(1000h),那么每一个Section的起始地址都应该是4096(1000h)的整数倍。如果第一个Section的地址是401000h,大小为100个字节。那么下一个Section的起始地址为402000h.。两个Section之间的空间大部分是空的,未用的。

l         FileAlignment每一个Section的磁盘对齐粒度。比如,此值为512(200h),那么每一个Section在文件内的偏移位置都是512(200h)的整数倍。与SectionAlignment同理。

l         SizeOfImagePE文件在内存空间整个映像的大小。包含所有的头及按SectinAlignment对齐的所有的Section

l         SizeOfHeaders所有的头加上Section表的大小。也就是文件大小减去文件中所有Section的大小。可以用这个值获取PE文件中第一Section的位置。

l         DataDiretory16IMAGE_DATA_DIRECTORY结构的数组。每一个成员都对应一个重要的数据结构,比如输入表,输出表等。

 

有两个地方需要注意:

l         如果PE header里的最后两个字段被赋予一个伪造的值的话,比如:

n         LoaderFlags = ABDBFFFFh    (其默认值为0)

n         NumberOfRvaAndSizes = DFFDEEEEh (其默认值为10h)

一些调试工具或反编译工具会认为这个PE文件是损坏的。有的会直接执行,如果是病毒的话,就会被直接感染;有的则会重启工具。所以最好在查看调试一个PE文件前,先看一下这里的取值是否被人赋予一个伪造的很大的值。如果是的话,先修改成默认的值。

l         有人可能注意到在一些PE文件(MS的链接器链接的PE文件)DOS Stub部分跟PE header部分之间存在一部分垃圾数据。标识为其倒数第二非0的双字节是一个“Rich ”。这部分数据包含了一些加密数据,来标识编译这个PE文件的组件。可用来检举某些病毒程序所编译的程序来自哪台机器。

 

四、        数据目录结构(Data Directory)

DataDirectoryOptionalHeader的最后128个字节,也是IMAGE_NT_HEADERS的最后一部分数据。它由16IMAGE_DATA_DIRECTORY结构组成的数组构成。IMAGE_DATA_DIRECTORY的结构如下:

 

每一个IMAGE_DATA_DIRECTORY都是对应一个PE文件重要的数据结构。他们分别如下:

VirtualAddress指的是对应数据结构的RVA地址;iSize指的是对应数据结构的大小(字节单位)。一个PE文件一般只包含其中的一部分,也就是其中一部分数据结构是有数据的;另一部分则都是0。比如,EXE文件一般都存在IMAGE_DIRECTORY_ENTRY_IMPORT(输入表),而不存在IMAGE_DIRECTORY_ENTRY_EXPORT(输出表)。而DLL则两者都包含。下图就是某一个PE文件的数据目录:

 

 

五、        Section表。

Section表紧跟在PE header后面。由IMAGE_SECTION_HEADER数据结构组成的数组。每一个包含了对应SectionPE文件中的属性和偏移位置。

这里不是所有的成员都是有用的。

l         Name: 块名,这是一个8ASCII码名,用来定义块名。多数块名以一个"."开始(如.text),尽管许多PE文档都认为这个"."实际上并不是必须的。值得注意的是,如果块名超过8位,则最后的NULL不存在。带有一个"$"的区块名字会从链接器那里得到特殊的对待,前面带"$"的相同名字的区块被合并,在合并后的区块中它们是按"$"后面的字符字母顺序进行合并的。

 

l         Misc.VirtualSize : 指出实际的、被使用的区块大小。如果VirtualSize大于SizeOfRawData,那么SizeOfRawData来自于可执行文件初始化数据的大小,与VirtualSize相差的字节用0填充。这个字段在OBJ文件中设为0

 

l         VirtualAddress : 该块装载到内存中的RVA。这个地址是按照内存页对齐的,它的数值总是SectionAlignment的整数倍。在MS工具中,第一块的默认RVA1000H.OBJ中,该字段没意义。如果该值为1000H, PE文件被加载到400000H,那么该Section的起始地址为401000H

 

l         SizeOfRawData : 该块在磁盘文件中所占的大小。在可执行文件中,这个值必须是PE头部指定的文件对齐大小的倍数。如果是0,则说明区块中的数据是未初始化的。该块在磁盘文件中所占的大小,这个数值等于VirtualSize字段的值按照FileAlignment的值对齐以后的大小。例如,FileAlignment的大小为1000H,如果VirtualSize中的块长度为2911,SizeOfRawData3000H}

 

l         PointerToRawData : 该块在磁盘文件中的偏移。对于可执行文件,这个值必须是PE头部指定的文件对齐大小的倍数。

 

l         PointerToRelocations : 这部分在EXE文件中无意义。在OBJ文件中,表示本块重定位信息的偏移量。在OBJ文件中如果不是零,则会指向一个IMAGE_RELOCATION的数据结构。

 

l         NumberOfRelocations : PointerToRelocations指向的重定位的数目。

 

l         NumberOfLinenumbers : NumberOfRelocations指向的行号的数目,只在COFF样式的行号被指定时使用。

 

l         Characteristics :  块属性,该字段是一组指出块属性(如代码/数据/可读/可写等)的标志。多个标志值通过OR操作形成Characteristics的值。这些标志很多都可以通过链接器/SECTION选项设置。

数据位在Windows.inc中的预定义

1时的含义

IMAGE_SCN_CNT_CODE (00000020H)

节中包含代码

IMAGE_SCN_CNT_INITIALIZED_DATA (00000040H)

节中包含已初始化数据

IMAGE_SCN_CNT_UNINITIALIZED_DATA (00000080H)

节中包含未初始化数据

25

IMAGE_SCN_MEM_DISCARDABLE (02000000H)

节中的数据在进程开始后将被丢弃

26

IMAGE_SCN_MEM_NOT_CACHED (04000000H)

节中的数据不会经过缓存

27

IMAGE_SCN_MEM_NOT_PAGED (08000000H)

节中的数据不会被交换到磁盘

28

IMAGE_SCN_MEM_SHARED (10000000H)

节中的数据将被不同的进程所共享

29

IMAGE_SCN_MEM_EXECUTE (20000000H)

映射到内存后的页面包含可执行属性

30

IMAGE_SCN_MEM_READ (40000000H)

映射到内存后的页面包含可读属性

31

IMAGE_SCN_MEM_WRITE (80000000H)

映射到内存后的页面包含可写属性

 

 

六、        PE文件各个Section

PE文件的Sections部分包含了文件的内容。包括代码,数据,资源和其他可执行信息。每一个Section由一个头部和一个数据部分组成。所有的头部都存放在紧跟PE header后的Section表内。

1.       执行代码。

NT Windows系统内,所有的PE文件的代码段都存放在一个Section内,通常命名为.text(MS)CODE(Borland)。这一段包含了早先提起的AddressOfEntryPoint多指地址的指令及输入表中的jump thunk table

2.       数据。

l         .bss段存放未初始化的数据,包括函数内或源模块内声明的静态变量。

l         .rdata段存放只读数据,比如常字符串,常量,调试指示信息。

l         .data 段存放其他所有的数据(除了自动化变量,其存放在栈中)。比如程序的全局变量。

3.       资源。

.rsrc段包含了一个模块的资源信息。以资源树的结构存放数据。需要用工具来查看。

4.       输出数据。

.edata段包含了PE文件的输出目录(Export Directory)

5.       输入数据。

.idata包含了PE文件的输入目录和输入地址表。

6.       调试信息。

调试信息存放在.debug段。PE文件也支持单独的调试文件。Debug段包含调试信息,但是调试目录却存放在.rdata内。

7.       线程局部存储。(TLS)

Windows支持每一个进程包含多个线程。每一个线程有其私有的存储空间(TLS)去存放线程自身的数据。链接器都会为进程创建一个.tls段来存放TLS模板。当进程创建一个线程时,系统就会按照这个模板创建一个线程私有的局部存储空间。

8.       基重定位。

当加载器加载PE文件到内存的时候,有时候不一定是其预期的基地址。那么就需要调整内部指令的相对地址。所有需要调整的地址都存放在.reloc段内。

七、输出Section.

这个SectionDLL关系比较密切。DLL一般定义两种函数,内部使用的,和输出到外部给其他调用程序使用的。输出到外部的函数就存储在这个Section内。

DLL输出函数分两种方式,通过名称和通过序号输出。当其他程序需要调用DLL的时候,调用GetProcAddress,通过设置需要调用的函数名称或函数序号可以调用DLL内部输出的函数。

那么GetProcAddress是怎么获取DLL中真正的输出函数地址呢?以下是详细的解说。

PE头的数据目录(DATA DIRECTORY)数组的第一个成员对应的(通过其中的RVA地址可获得)数据结构是IMAGE_EXPORT_DIRECTORY(这里称为输出目录)

成员

大小

描述

Characteristics

DWORD

未定义,总是0

TimeDateStamp

DWORD

输出表的创建时间。与IMAGE_NT_HEADER.FileHeader.TimeDateStamp有相同的定义

MajorVersion

WORD

输出表的主版本号。未使用,为0

MinorVersion

DWORD

输出表的次版本号。未使用,为0

nName

DWORD

指向一个ASCII字符串的RVA,这个字符串是与这些输出函数关联的DLL的名称(比如,Kernel32.dll)。这个值必须定义,因为如果DLL文件的名称如果被修改,加载器将使用这里的名称。

nBase

DWORD

这个字段包含用于这个可执行文件输出表的起始序数值(基数)。正常情况下为1,但不是一定是。当通过序数来查询一个输出函数时,这个值会被从序数里减去。(比如,如果nBase = 1,被查询的函数的序数是3,那么这个函数在序号表的索引是3 -1 = 2)。

NumberOfFunctions

DWORD

输出地址表(EAT)的条目数。其中一些条目可能是0,意味着这个序数值没有代码和数据输出。

NumberOfNames

DWORD

输出名称表(ENT)的条目数。这个值总是大于或等于NumberOfFunctions。小于的情况发生在符号只通过序数来输出时。另外,当被赋值的序数里有数字间隔时也会有小于的情况。这个值也是输出序数表的长度。

AddressOfFunctions

DWORD

输出地址表(EAT)RVA。输出地址表本身是一个RVA数组,数组中的每一个非零的RVA都对应一个被输出的符号。

AddressOfNames

DWORD

输出名称表(ENT)RVA。输出名称表本身是一个RVA数组。数组中的每一个非零的RVA都向一个ASCII字符串。每一个字符串都对应一个通过名称输出的符号。这个表是排序。这允许加栽器在查询一个被输出的符号时可用二进制查找方式。名称的排序是二进制的,而不是按字母。

AddressOfNameOrdinals

DWORD

输出序数表(EOT)的RVA。这个表将ENT中的数组索引映射到相应的输出地址条目。

 

实际上,IMAGE_EXPORT_DIRECTORY结构指向三个数组和一个ASCII字符串表。其中重要的是输出地址表(EAT,AddressOfFunctions指向的表), 输出函数地址指针(RVA)构成了这个表。而ENTEOT则是可以一起合作来获取EAT里对应的地址数据。下图演示了这个过程。

 

这个被加载的DLL的名称是F00.DLL。总共输出了四个函数,其RVA地址分别为0x4000420x4001560x4012560x400520。一个外部调用程序需要调用其中一个名为”Bar”的函数,那么它先在输出名称表(ENT)里查找名称为Bar的函数,找到后,根据其在输出序号表(EOT)中对应的索引号,获取其中的数值为EAT中的索引值,这里是4,然后从EAT中根据索引4获取其真正的RVA地址0x400520。以下是几个注意点:

l         输出序号表(EOT)的存在就是为了是EATENT之间产生关联。每一个ENT内的成员(函数名)有且只有一个EAT内的成员(函数地址)对应。但是一个EAT内的成员并不是只有一个ENT内的成员对应。比如,有的函数存在别名的话,就会出现多个ENT内的成员都对应一个EAT内的成员。

l         如果已经获得一个函数的序号值,那么就可以直接到EAT内获得其RVA地址,而不需要经过ENTEOT进行查找。但是这样的按序号输出的DLL不易于维护。

l         通常情况下,EAT的个数(NumberOfFunctions)必须小于或等于ENT的个数(NumberOfNames)。只有在一个函数按序号输出时(其在ENTEOT表里没有对应的数据),ENT的数量才有可能少于EAT的数量。比如,总共有70个函数输出,但是在ENT表里只有40个,这就意味着剩余的30个函数是靠序号输出的。那么我们如何知道哪些是直接靠序号输出的呢?只有通过排除法来获得。把存在在EOT表里的序号从EAT里排除出去,剩下的就是靠序号输出的函数。

l         当通过一个序号值来获取EAT内的函数RVA时,需要把这个序号值减去nBase的值来获取在EAT表里真正的索引位置。而通过名称查找则不需要这么做。

l         输出转向。某些时候,你从一个DLL中调用的一个函数可能位于另一个DLL中。这就叫输出转向。比如,Kernel32.dll中的HeapAlloc就是转到调用NTDLL.dll中的RtlAllocHeap。这种转向是在链接的时候,在.DEF文件中定义一个特殊的指令来实现的。那么当一个函数被转向后,在其所在EAT表里对应的数据便不是其地址,而是一个指向表明被转向的DLL和函数的ASCII字符串的地址指针。

上图就是Kernel32.dll的输出函数表,其中HeapAllocRVA0x00009048就是一个指向“NTDLL.RtlAllocHeap”的指针。

 

 八         输入Section.

  输入Section通常位于.idata段内。它包含了所有程序需要用到的来自其他DLL的函数的信息。Windows加载器负责加载所有程序用到的DLL到进程空间。然后为进程找到所有其需要用到的函数的地址。下面描述这个过程:

 

 

PE头的数据目录(DATA DIRECTORY)数组的第二个成员对应的(通过其中的RVA地址可获得)数据结构是输入表。输入表是一个IMAGE_IMPORT_DESCRIPTOR数据结构的数组。没有字段表明这个数组的个数,只是它的最后一个成员的数据都为0。每一个数组成员都对应一个DLL。
 

成员
大小
描述
OriginalFirstThunk
DWORD
指向输入名称表(INT)的RVA。INT是由IMAGE_THUNK_DATA数据结构构成的数组。数组中的每一个成员定义了一个输入函数的信息,数组最后以一个内容为0的IMAGE_THUNK_DATA结束。
TimeDateStamp
DWORD
当执行文件不与被输入的DLL进行绑定时,这个字段为0。当以旧的方式绑定时,这个字段包括时间/日期。当以新的样式绑定时,这个字段为-1。
ForwarderChain
DWORD
这是第一个被转向的API的索引。老样式绑定的定义。
Name
DWORD
指向被输入DLL的ASCII字符串的RVA。
FirstThunk
DWORD
指向输入地址表(IAT)的RVA。IAT也是一个IMAGE_THUNK_DATA数据结构的数组。

 
由上表可知,输入表主要是通过IMAGE_THUNK_DATA这个数据结构导入函数。下面是IMAGE_THUNK_DATA的描述:
这是一个DWORD联合体数据结构。其实这里对输入表有意义的字段只有两个,Ordinal和AddressOfData。当这个DWORD数据的最高位为1的时候,代表函数以序号的方式导入,Ordinal的低31位就是输入函数在其DLL内的导出序号。当这个DWORD的数据最高位为0的时候,代表函数以字符串方式导入。AddressOfData就是一个指向用来导入函数名称的IMAGE_IMPORT_BY_NAME的数据结构的RVA。(这里用来判断最高位的值0x8000000,预定义值为IMAGE_ORDINAL_FLAG32。)
 
 
l         Hint字段也表示函数的序号,主要是用来便与加载器快速查找在导入的DLL的函数导出表,当通过这个序号查找到的函数跟所要导入的函数不匹配时,就改为通过名称查找。不过这个字段是可选的,有些编译器把它设置为0。
l         Name1字段定义了导入函数的名称字符串,这是一个以0为结尾的字符串。
 
整个过程有点复杂,下图给出一个相对清晰的描述。
1. 加载器首先读入IMAGE_IMPORT_DESCRIPTOR,获得需要加载的动态库User32.DLL。
2. 加载器根据OriginalFirstThunk或FirstThunk所指向的IMAGE_THUNK_DATA数组的RVA来获取真正的输入函数名称表(INT)和输入函数地址表(IAT)。这里这两个表所指向的是同一个IMAGE_IMPORT_BY_NAME数据结构的RVA。
3. 加载器根据IMAGE_IMPORT_BY_NAME的序号或名称到导入的DLL(user32.dll)函数导出表中获取导入函数的地址。然后把这个地址替换掉FirstThunk所指向的函数输入地址表中的数据。
上图已经说明了为什么会存在两个一模一样的IMAGE_THUNK_DATA数组。答案就是在这个PE文件被装入内存后,FirstThunk所指向的IMAGE_THUNK_DATA内的值将被改为用来存储导入函数的真正的地址。我们称之为IAT(Import Address Table). 其实在数据目录表DATA_DIRECTORY中的第13项(索引为12)直接给出了这个IAT的地址和大小. 可以直接通过数据目录快速获得这个IAT表. 但是这样还不足于说明为什么会存在两个一样的IMAGE_THUNK_DATA数组。INT好象没有存在的必要。这里要涉及到一个绑定的概念。
绑定:
l         在加载器加载PE文件的时候,先需要检查输入表获取要输入的DLL的名称,然后把DLL映射到进程的地址空间。再检查IAT表里的IMAGE_THUNK_DATA数组所指向的字符串获取要输入函数的名称,然后用输入函数的地址替换掉IMAGE_THUNK_DATA数组内的数据。整个过程需要相对比较长的时间。如果事先在链接的时候就把这些地址写入IAT中,那么就会节省很多时间。这就是绑定的由来。
l         再绑定后,PE文件IAT表里放着是导入DLL输出函数的实际内存地址。要使绑定的结果能正常运行,需要两个条件:
n         在加载PE文件所需的DLL的时候,DLL应该被映射到它们自己PE头里定义好的ImageBase这个地址。
n         被执行绑定后,PE文件所导入DLL的函数导出的函数表里的函数符号的位置不能发生改变。
l         这两个条件当然很难在长时间内很难满足。比如,这个被导入的DLL发生了变化,增加了新的函数输出。那么其原来输出表内的函数符号的位置发生了变化。那么这个时候,原先绑定的结果就会发生错误。为了解决这个问题,所以就同时定义了INT这个表。让它做为IAT的备份。一旦预先绑定好的IAT发生了错误,那么加载器便会从INT里获取所需要的信息。
这就是为什么会存在两个一模一样的IMAGE_THUNK_DATA数组真正的缘由。微软的链接器一般总会在生成IAT的同时生成一个INT;而Borland的链接器却只生成IAT。所以Borland生成的PE文件是不能被绑定的。
那么,当加载器加载PE文件的时候,需要判断当前的绑定是否有效。在数据目录(Data Directory)的第12项(序号为11)所指向的一组数据结构IMAGE_BOUND_IMPORT_DESCRIPTOR就是用来检查这个有效性的。

成员
大小
描述
TimeDateStamp
DWORD
必须与被输入的DLL的PE头内的TimeDateStamp一样,如果不一致,那么加载器就会认为绑定的对象有误,需要重新修补输入表。
OffsetModuleName
WORD
第一个IMAGE_BOUND_IMPORT_DESCRIPTOR结构到被输入DLL名称的偏移(非RVA)。
NumberOfModuleForwarderRefs
WORD
包含紧跟在这个结构后面IMAGE_BOUND_FORWARDER_REF的数目。

这个结构跟IMAGE_BOUND_IMPORT_DESCRIPTOR其实很象除了最后一个成员。它主要用于,在被导入的DLL中的某一个函数是转向导出时,这个结构就用来给出所转向到的函数的信息。
延迟加载:
除了通过加载器建立IAT表以外,程序调用外部DLL函数还有另外一种方式。就是先通过LoadLibrary动态加载DLL,然后用GetProcAddress获取所需函数的地址。这种方式称之为“延迟加载”。
数据目录(Data Directory)第14个成员(序号是13)IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT条目就是指向延迟加载的数据。这个数据就是由一个名叫ImgDelayDescr数据结构组成的数组。
 
ImgDelayDescr = packed record
grAttrs: DWORD; 
szName: DWORD; 
phmod: PDWORD;
pIAT: TImageThunkData32;
pINT: TImageThunkData32; 
pBoundIAT: TImageThunkData32;
pUnloadIAT: TImageThunkData32;
dwTimeStamp: DWORD;
end;

成员
描述
grAttrs
设为1的时候,下面的各个成员都是RVA,否则是VA(虚拟地址)。
szName
指向一个DLL名称的RVA。
phmod
指向一个HMODULE的RVA。
pIAT
指向DLL的IAT的RVA。
pINT
指向DLL的INT的RVA。
pBoundIAT
可选的绑定IAT的RVA。
pUnloadIAT
指向DLL的IAT的未绑定拷贝
dwTimeStamp
延迟装载的输入DLL的时间/日期。通常是0。

 
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 qq号被限制解封怎么办 买的桌子会晃怎么办 车子座椅皮坏了怎么办 裤子被椅子刮了怎么办 脚麻了被别人动怎么办 毛孔又粗又黑怎么办 手臂睡觉压麻了怎么办 睡觉压的胳膊麻怎么办 摔跤摔到腰肿了怎么办 牛奶喝多了拉稀怎么办 doc文档下载是乱码怎么办 家人被教练技术骗了怎么办 十个月宝宝不爱喝水怎么办 离婚后孩子找爸爸怎么办 离婚后孩子要找爸爸怎么办 断奶期间胸涨的难受怎么办 断奶胸涨的很疼怎么办 过了麦季身上老痒怎么办 过麦时候皮肤痒怎么办 颈部起红疙瘩痒怎么办 脚底痒身上痒该怎么办 生完孩子屁股疼怎么办 pr打开工程文件无响应怎么办 娃娃和老师有隔阂了怎么办 大人字写得不好怎么办 不会写好看的字怎么办 无限量流量限速了怎么办 长期化妆后皮肤变黄怎么办 手机密码忘记了打不开怎么办 手机解压包不知道密码怎么办 手机屏图标没了怎么办 b站页面卡顿怎么办 被全景视觉骗了怎么办? 逆战进去没声音怎么办 Mac电脑ai卡住了怎么办 pr字幕打不了字怎么办 pr手写字多笔画怎么办 家中挖矿噪音很大怎么办 学了栏目包装不想干怎么办 在文本打字乱了怎么办 体考后觉得成绩有出入怎么办