PE文件结构学习
PE:Portable Executable File Format(可移植的执行体)。Windows平台主流可执行文件格式。.exe与.dll文件都是PE格式。32位的叫做PE32,64位的叫做PE32+。PE文件格式定义在winnt.h头文件中。
PE文件格式总览:
PE文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,组成一个很大的结构。文件的内容被分割为不同的区块(Section,又名区段,节等),区块中包含代码或数据,各个区块按页边界来对齐,区块没有大小限制,是一个连续的结构。每个块都有它自己在内存中的一套属性,区块是相同属性的数据的一个集合。
PE文件不是作为单一内存映射文件被装入内存的。PE文件通过PE装载器遍历PE文 件并决定文件的哪一部分被映射,这种映射方式是将文件较高的偏移位置映射到较高的内存地址,当磁盘文件被装入内存中,磁盘上的数据结构布局和内存中的数据 结构布局是一致的。这样的好处是在磁盘中的的内容,在载入内存中后也可以找到同样的信息,但数据间的相对位置可能改变。
DOS头,PE头,块表在内存中的偏移和在磁盘中的偏移是一致的。其余的内存偏移和磁盘偏移是需要转换的。
各种地址的概念:
基址(Image Base):PE文件装入内存后的起始地址。
相对虚拟地址(Relative Virtual Address,RVA):在内存中相对于PE文件装入地址的偏移位置,是一个相对地址。
虚拟地址(Virtual Address,VA):装入内存中的实际地址。
文件偏移(Fill Offset):PE文件存储在磁盘上时,相对于文件头的偏移位置。16进制文件编辑器打开后的地址为文件偏移地址。
虚拟地址(VA) = 基址(Image Base) + 相对虚拟地址)(RVA)
将RVA转换为File Offset,给大家一个非常经典的公式:
设:VK为相对虚拟地址RVA与文件偏移地址File Offset的差值
VA=ImageBase+RVA
File Offset = RVA ---VK
File Offset = VA---ImageBase---VK
PE文件各部分结构(为winnt.h中的结构体定义)
实例程序为VC++6.0自动生成的窗口程序
所标注的偏移地址为16进制偏移地址
MS-DOS头:
DOS MZ头与DOS stub合称DOS文件头
typedef struct _IMAGE_DOS_HEADER {
// DOS .EXE header
WORD
e_magic;
// Magic number
WORD
e_cblp;
// Bytes on last page of file
WORD
e_cp;
// Pages in file
WORD
e_crlc;
// Relocations
WORD
e_cparhdr;
// Size of header in paragraphs
WORD
e_minalloc;
// Minimum extra paragraphs needed
WORD
e_maxalloc;
// Maximum extra paragraphs needed
WORD
e_ss;
// Initial (relative) SS value
WORD
e_sp;
// Initial SP value
WORD
e_csum;
// Checksum
WORD
e_ip;
// Initial IP value
WORD
e_cs;
// Initial (relative) CS value
WORD
e_lfarlc;
// File address of relocation table
WORD
e_ovno;
// Overlay number
WORD
e_res[4];
// Reserved words
WORD
e_oemid;
// OEM identifier (for e_oeminfo)
WORD
e_oeminfo;
// OEM information; e_oemid specific
WORD
e_res2[10];
//
Reserved words
LONG
e_lfanew;
// File address of new exe header
}
IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
在16进制编译器中如下:
结构体第一个为DOS头标志,在16进制编译器中查看为
4D 5A 90 00
MZ
最后一个为LONG型数据指向PE头的RVA,也就是在000000E0的位置是PE头。
PE文件头:
PE头定义如下:
IMAGE_NT_HEADERS STRUCT
{
DWORD Signature;
//PE文件头标志,为ASCII的“PE”,+0h
IMAGE_FILE_HEADER FileHeader;
//+4h
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
//+18h
}
PE头包含2个结构体,IMAGE_FILE_HEADER
与 IMAGE_OPTIONAL_HEADER32
typedef struct _IMAGE_FILE_HEADER {
WORD
Machine;
//+04h
WORD
NumberOfSections;
//+06h
文件的区块数目
DWORD
TimeDateStamp;
//+08h
DWORD
PointerToSymbolTable;
//+0Ch
DWORD
NumberOfSymbols;
//+10h
WORD
SizeOfOptionalHeader;
//+14h
IMAGE_OPTIONAL_HEADER32
//
结构大小
WORD
Characteristics;
//+16h
文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
在此文件中
通过DOS头部最后一个数据找到PE头的偏移地址为000000E0,找到PE头位置。
从上图可以看出,区块数目为0006,也就是6个区块,可以在LoadPE中看到:
可选头大小(SizeOfOptionalHeader)为00E0,都可以在LoadPE中得到验证。
IMAGE_OPTIONAL_HEADER结构:
00E0转化为10进制为224字节,也就是IMAGE_OPTIONAL_HEADER结构的大小为224字节。
此部分为IMAGE_OPTIONAL_HEADER结构部分。
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD
Magic;
//+18h
BYTE
MajorLinkerVersion;
//+1Ah
BYTE
MinorLinkerVersion;
//+1Bh
DWORD
SizeOfCode;
//+1Ch
DWORD
SizeOfInitializedData;
//+20h
DWORD
SizeOfUninitializedData;
//+24h
DWORD
AddressOfEntryPoint;
//+28h
程序执行入口的RVA,OEP
DWORD
BaseOfCode;
//+2Ch
DWORD
BaseOfData;
//+30h
DWORD
ImageBase;
//+34h
程序默认装入地址,基址
DWORD
SectionAlignment;
//+38h
内存对齐
DWORD
FileAlignment;
//+3ch
文件对齐
WORD
MajorOperatingSystemVersion;//+40h
WORD
MinorOperatingSystemVersion;//+42h
WORD
MajorImageVersion;
//+44h
WORD
MinorImageVersion;
//+46h
WORD
MajorSubsystemVersion;
//+48h
WORD
MinorSubsystemVersion;
//+4Ah
DWORD
Win32VersionValue;
//+4Ch
DWORD
SizeOfImage;
//+50h
DWORD
SizeOfHeaders;
//+54h
DWORD
CheckSum;
//+58h
校验和
WORD
Subsystem;
//+5Ch
WORD
DllCharacteristics;
//+5Eh
DWORD
SizeOfStackReserve;
//+60h
DWORD
SizeOfStackCommit;
//+64h
DWORD
SizeOfHeapReserve;
//+68h
DWORD
SizeOfHeapCommit;
//+6ch
DWORD
LoaderFlags;
//+70h
DWORD
NumberOfRvaAndSizes;
//+74h
数据目录表项数
IMAGE_DATA_DIRECTORY
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
//+78h
} IMAGE_OPTIONAL_HEADER32,
*PIMAGE_OPTIONAL_HEADER32;
从上面可以看出以下数据:
OEP的位置为
000017B0
Image Base为
00400000
SectionAlignment:当被装入内存时的区块大小。每个区块被装入的地址必定是本字段指定数值的整数倍。默认的对齐尺寸是目标CPU的页尺寸。对于运行在windows 9x/Me 下的用户模式可执行文件,最小的对齐尺寸是一页1000h(4kb)。从上面可以得出此程序的内存对齐为1000h
FileAlignment:磁盘上PE文件内的区块对齐大小,组成块得原始数据必须保证从本字段的倍数地址开始。从上面可以看出此文件的文件对齐为1000h。
NumberOfRvaAndSizes为00000010,转化为十进制为16,从Windows NT发布以来一直为16.
下面从LoadPE中看一下。
DataDirectory[16]:数据目录表,由数个相同的IMAGE_DATA_DIRECTORY结构组成,指向输出表,输入表,资源块等数据。
typedef struct _IMAGE_DATA_DIRECTORY
{
DWORD
VirtualAddress;
//数据块的起始RVA
DWORD
Size;
//数据块长度
} IMAGE_DATA_DIRECTORY,
*PIMAGE_DATA_DIRECTORY;
从前8个字节可以看出此exe文件的没有导出表,此后的部分可以与LoadPE中的数据一一对应。
区块:
区块表:
typedef struct _IMAGE_SECTION_HEADER
{
BYTE
Name[IMAGE_SIZEOF_SHORT_NAME];
//8个字节的区块名
union {
//区块尺寸
DWORD
PhysicalAddress;
DWORD
VirtualSize;
} Misc;
DWORD
VirtualAddress;
//区块的RVA地址
DWORD
SizeOfRawData;
//文件对齐后的尺寸
DWORD
PointerToRawData;
//文件偏移
DWORD
PointerToRelocations;
DWORD
PointerToLinenumbers;
WORD
NumberOfRelocations;
WORD
NumberOfLinenumbers;
DWORD
Characteristics;
//区块的属性
} IMAGE_SECTION_HEADER,
*PIMAGE_SECTION_HEADER;
在IMAGE_FILE_HEADER结构的第二个数据标识了文件的区块数目,在偏移为6h的地方可以找到区块数目为0006,也就是说这个实例成有有6个区段,如下图所示:
每个区段的长度为40个字节。
拿第一个区段举例,从上图可以看出:
区段的名称为:.text段
区段的尺寸为:00021000
区块的RVA为:00001000
区块的文件对齐后大小为:00021000
区块的文件偏移为:00001000,
区块的属性为:60000020
Characteristics:块属性。该字段是一组指出块属性(如代码/数据/可读/可写等)的标志。
多个标志值求或即为Characteristics的值。这些标志中的很多都可以通过连接器的/SECTION选项设置。
字段值
用途
IMAGE_SCN_CNT_CODE
00000020h |
包含代码,常与10000000h一起设置
IMAGE_SCN_CNT_INITIALIZED_DATA
|
该块包含已初始化的数据
00000040h |
IMAGE_SCN_CNT_UNINITIALIZED_DATA
|
该块包含未初始化的数据
00000080h |
IMAGE_SCN_MEM_DISCARDABLE
|
该块可被丢弃,因为它一旦为装入后,进程就
02000000h |
不再需要它了,比如.reloc(重定位块)
IMAGE_SCN_MEM_SHARED
10000000h |
该块为共享块
IMAGE_SCN_MEM_EXECUTE
20000000h |
该块可以执行,通常当000000020h标志被设
| 置时,该标志也被设置
IMAGE_SCN_MEM_READ
40000000h |
该块可读。可执行文件中的块总是设置此标志
IMAGE_SCN_MEM_WRITE
80000000h |
该块可写,如果可执行文件没有设置该标志,|装载程序就会将内存映像页标记为可读或可 |执行
上图为LoadPE中区段表的截图
数据目录表中的数据,是在区段中的数据。而区段表的数据,是指向区段边界的数据。
区块的名字可以随意改变,以下只是常见的一些区段名称。
常见区段:
.text
默认的代码区段。链接器不同可能导致代码区段不同,有可能是.code段
.data
默认的读/写数据区块。全局变量、静态变量一般放在这里。
.rdata
默认的只读数据区块。
.idata
包含其他外表DLL的函数及数据信息,即输入表。常被合并
.edata
输出表。常被合并
.rsrc
资源,包含模块的全部资源。
.bss
未初始化的数据。
.crt
用于支持C++运行时(CRT)所添加的数据。
.tls
tls的意思是线程局部存储器,用于支持通过__declspec(thread)声明的线程局部
存储变量的数据。这包括数据的初始化,也包括运行时所需要的额外变量
.reloc
可执行文件的基址重定位。基址重定位一般仅是DLL需要的,而不是EXE。
.sdata
相对于全局指针的可被定为的“短的”读/写数据。
.srdata
相对于全局指针的可被定为的“短的”只读数据。
.pdata
异常表。
.debug$S
OBJ文件中Codeview格式的符号。
.debug$T
OBJ文件中Codeview格式的类型记录。
.debug$P
当使用预编译的头时,它可以再OBJ文件中找到
.drectve
包含链接器命令,只能在OBJ中找到。
.didat
延迟装入的输入数据,只能在非Release模式的可执行文件中找到。
当编程从PE文件中读取需要的内容时,如输入表、输出表等,不能以区块名称作为参考,正确的方法是按照数据目录表中的字段进行定位。在数据目录表中,找到数据的RVA,对比区段表,查看所找数据是在哪一个区段当中。
可以创建和命名自己的区块。
区块的对齐值:
区块的大小是要对齐的,有两种对齐值,一种用于磁盘文件内,另一种用于内存中。PE文件头之处了这两个值,他们可以不同。
PE文件头里的FileAlignment定义了磁盘区块的对齐值。每一个区块从对齐值的倍数的偏移位置开始。而区块的实际代码或数据的大小不一定刚好是这么多,所以在不足的地方一般用00h来填充,这就是区块的间隙。
PE文件头里SectionAlignment定义了内存中区块的对齐值。PE文件被映射到内存中时,区块总是至少从一个页边界处开始,也就是说,当一个PE文件被映射到内存中,每个区块的第一个字节对应于某个内存页。在x86系列CPU中,页是按照4KB(1000h)来排列的,在IA-64上,是按8KB(2000h)来排列的。所以在x86系统中,PE文件区块的内存对齐值一般等于1000h,每个区块按1000h的倍数的内存偏移位置开始。
建立一个区块在文件中的偏移和在内存中的偏移相同的PE文件是可能的,会使可执行文件变大。
文件偏移与虚拟地址转换:
由于一些PE文件为减少体积,磁盘对齐值不是一个内存页1000h,而是200h,当这类文件被映射到内存后,同一数据相对于文件头的偏移量在内存中和磁盘文件中是不同的,这样就存在着文件偏移地址与虚拟地址的转换问题。如果磁盘对齐与内存对齐相同,则不需要转换。
由上图可以看出,文件被映射到内存,DOS文件头,PE文件头,区块表的偏移位置和大小都没有发生改变。而各区块映射到内存后,起偏移位置发生了改变。
转换需要前面提到的一个公式:
设:VK为相对虚拟地址RVA与文件偏移地址File Offset的差值
VA=ImageBase+RVA
File Offset = RVA ---VK
File Offset = VA---ImageBase---VK
也可以使用LoadPE直接转换:
输入表:
可执行文件使用来自于其他DLL的代码或数据时,成为输入。当PE文件装入时,windows加载器(PE装载器)的工作之一就是定位所有被输入的函数和数据,并且让正在被装入的文件可以使用那些地址。这个过程是通过PE文件的输入表(Import Table,简称IT,也成为输入表)来完成,输入表中保存的是函数名和其驻留的DLL名等动态链接所需的信息。
输入函数的调用:
输入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于相关的DLL文件中,在调用者程序中只保留相关函数信息,如函数名,DLL文件名等。对于磁盘上的PE文件来说,它无法得知这些输入函数在内存中的地址。只有当PE文件被装入内存后,windows加载器(PE装载器)才将相关DLL装入,并将调用输入函数的指令和函数实际所处的地址联系起来。
当应用程序调用一个DLL的代码和数据时,那它正在隐含链接到DLL,这个过程完全由window加载器(PE装载器)完成。另一种是运行期的显示链接,这意味着必须确定目标DLL已经被加载,然后寻找API地址,这几乎总是通过调用LoadLibrary和GetProcAddress来完成。
当隐含地链接一个API时,类似LoadLibrary和GetProcAddress的代码始终在执行,只不过这是windows装载器(PE加载器)自动完成的。装载器还保证PE文件所需的任何附加的DLL都已被载入。
一个PE文件所需要的所有的函数都被包含在内。
在PE文件中,有一组数据结构,他们分别对应着每个被输入的DLL,每一个这样的结构都给出了被输入的DLL的名称并指向一组函数指针。这组函数指针被称为输入地址表(Import Address Table,简称IAT)。每一个被引入的API在IAT里都有它自己保留的位置,在那里它将被windows加载器写入输入函数的地址。一旦模块被装入,IAT中包含所要调用输入函数的地址。
在IMAGE_DATA_DIRECTORY(数据目录表的第二项)为输入表,
RVA为0002A000
大小为0000003C
在LoadPE中可以得到确认:
与区段表进行对比后可以看出,导入表在.idata段当中。
输入表是一个IMAGE_IMPORT_DESCRIPTOR(简称IID)数组开始的。每个被PE文件隐式链接进来的DLL都有一个IID。在这个数组中,没有字段指出该结构数组的项数,但是他的最后一个单元为NULL,可以由此计算项数。
typedef struct
_IMAGE_IMPORT_DESCRIPTOR {
union {
// +00h
DWORD
Characteristics;
DWORD
OriginalFirstThunk; //指向输入名称表INT
};
DWORD
TimeDateStamp;
//+04h
DWORD
ForwarderChain;
//+08h
DWORD
Name;
//+0Ch DLL名称的指针
DWORD
FirstThunk;
//10h
指向输入地址表IAT。
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR
UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
OriginalFirstThunk与FirstThunk都是IMAGE_THUNK_DATA结构的数组。
typedef struct _IMAGE_THUNK_DATA32
{
union {
PBYTE
ForwarderString;
//指向一个转向者字符串的RVA
PDWORD Function;
//被输入的函数的内存地址
DWORD Ordinal;
//被输入的API的序数值
PIMAGE_IMPORT_BY_NAME
AddressOfData;//指向IMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
每一个IMAGE_THUNK_DATA元素对应于一个从可执行文件输入的函数。两个数组的结束是通过一个值为0的IMAGE_THUNK_DATA元素来表示的。当IMAGE_THUNK_DATA值的最高位为1时,表示函数以序号方式输入,这时低31位(或者64位可执行文件的低63位)被看做一个函数序号。当双子的最高位为0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构。
typedef struct _IMAGE_IMPORT_BY_NAME
{
WORD
Hint;
BYTE
Name[1];
} IMAGE_IMPORT_BY_NAME,
*PIMAGE_IMPORT_BY_NAME;
输入地址表(IAT)
为什么有两个并行的指针数组指向IMAGE_IMPORT_BY_NAME结构呢?第一个数组(由Original First
Thunk所指向)是单独的一项,并且不可改写,成为INT,有时也成为提示名表。第二个数组(由First Thunk所指向)是由PE装载器重写的。PE装载器首先搜索OriginalFirstThunk,如果找到,加载程序迭代搜索数组中的每个指针,找到每个Image_IMPORT_BY_NAME结构所指向的输入函数的地址,然后加载器用函数真正入口地址来替代由FirstThunk数组中的一个函数入口,因此塔成为输入地址表(IAT)。程序加载后,输入表其他部分就不重要了,程序依靠IAT提供的函数地址就可正常运行。
文件偏移:28000-(2A000 – 28000)=28000
输入表的文件偏移为28000.
找到28000的位置,输入表是一个结构体数组,每个结构体20个字节,以20个字节的00为结束,由上图可以看出,此程序由两个DLL,从LoadPE看一下:
Name是指向DLL的指针,是一个RVA地址,我们可以查找一下
第一个IID的第4个成员为0002A4F2,转换成文件偏移为0000284F2
可以看出为USER32.dll,与LoadPE中的数据相同。
OriginalFirstThunk指向的IMAGE_THUNK_DATA数组位置,可以看出有21个函数。
再查找下FirstThunk指向的IMAGE_THUNK_DATA数组位置,
两个结构完全相同,也是有21个函数,我们在LoadPE中查看一下:
与LoadPE中USER32.dll中的函数个数相同。
查找第一个为
与LoadPE中显示相同。
在程序运行前,它的FirstThunk字段值也是指向一个地址串,而且和OriginalFirstThunk字段值指向的INT是重复的。系统在程序初始化时根据OriginalFirstThunk的值找到函数名,调用GetProcAddress函数且根据函数名取得函数的入口地址,然后用函数入口地址取代FirstThunk指向的地址传中对应的值(IAT)
这是一个从内存中抓取出来的,是PE文件映射到内存中的状态,上图为IAT。
此图为INT,两者已经不一样。
在OD中可以看到进行了一次函数调用,不过这个没有看到具体调用的是什么函数……
输入表部分的内容有点乱……
输出表:
Exe文件一般不存在输出表,而大部分DLL文件中存在输出表。
输出表格式如下:
typedef struct
_IMAGE_EXPORT_DIRECTORY {
DWORD
Characteristics;
DWORD
TimeDateStamp;
WORD
MajorVersion;
WORD
MinorVersion;
DWORD
Name;
//模块的真实名称
DWORD
Base;
//基数,加上序数就是函数地址数组的索引数
DWORD
NumberOfFunctions;
// AddressOfFunctions中元素个数
DWORD
NumberOfNames;
// AddressOfNames中元素个数
DWORD
AddressOfFunctions;
//
指向函数地址数组
DWORD
AddressOfNames;
//
函数名字的指针地址
DWORD
AddressOfNameOrdinals; //
指向输出序列号数组
} IMAGE_EXPORT_DIRECTORY,
*PIMAGE_EXPORT_DIRECTORY;
输出表为如上部分。
NumberOfFunctions
00000001
NumberOfNames
00000001
AddressOfFunctions 00004028
00001008
addressOfNames
0000402C
0000403E
AddressOfNameOrdinals
00004030
与LoadPE中数据进行比较,完全一致。
最后一个数据:
基址重定位:
当链接器生成一个PE文件时,它假设这个文件执行时会被装载到默认的基地址处,并且把code和data的相关地址都写入PE文件中。如果装入时按默认的值作为基地址装入,则不需要重定位。但如果可执行文件被装载到虚拟内存的另一个地址,链接器所登记的那个地址就是错误的,这时就需要用重定位表来调整。在PE文件中,它往往当度分为一块,用.reloc表示。
PE做法十分简单,他们并不参考外部DLL或模块中的其他Sections,而是把文件中所有可能需要修改的地址放在一个数组里。如果可执行文件不在首选的地址装入,那么文件中每一个定位都需要被修正。
在数据目录表中查找重定位表的RVA
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD
VirtualAddress;
//重定位数据开始RVA地址
DWORD
SizeOfBlock;
//重定位块得长度
// WORD
TypeOffset[1];
//重定项位数组
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION
UNALIGNED * PIMAGE_BASE_RELOCATION;
在16进制编译器中找到重定位表:
TypeOffset:是一个数组,数组每项大小为两个字节,共16位。又分为高4位与低12位,高4位代表重定位类型;低12位是重定位地址,他与VirtualAddress相加既是指向PE影响中需要修改的地址数据的指针。
图中最后4位用于对齐。
重定位数据
300F
3023
0000
0000
高4位
3
3
低12位
00f
023
加上virtualAddress 0000100f
00001023
为RVA
转化成文件偏移
60f
623
查找到的重定位数据。此为PE影响中需要修改的地址数据。
资源:
资源目录结构:
typedef struct
_IMAGE_RESOURCE_DIRECTORY {
DWORD
Characteristics;
DWORD
TimeDateStamp;
WORD
MajorVersion;
WORD
MinorVersion;
WORD
NumberOfNamedEntries;
//使用名字的资源条目的个数
WORD
NumberOfIdEntries;
//使用ID数字资源条目的个数
//
IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY,
*PIMAGE_RESOURCE_DIRECTORY;
资源目录入口结构:
typedef struct
_IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset:31;
DWORD NameIsString:1;
};
DWORD
Name;
//目录项的名字字符串指针或ID
WORD
Id;
};
union {
DWORD
OffsetToData;
//资源数据偏移地址或子目录偏移地址
struct {
DWORD
OffsetToDirectory:31;
DWORD
DataIsDirectory:1;
};
};
typedef struct
_IMAGE_RESOURCE_DIR_STRING_U {
WORD
Length;
WCHAR
NameString[ 1 ];
} IMAGE_RESOURCE_DIR_STRING_U,
*PIMAGE_RESOURCE_DIR_STRING_U;
资源数据入口:
typedef struct
_IMAGE_RESOURCE_DATA_ENTRY {
DWORD
OffsetToData;
//资源数据的RVA
DWORD
Size;
//资源数据的长度
DWORD
CodePage;
DWORD
Reserved;
} IMAGE_RESOURCE_DATA_ENTRY,
*PIMAGE_RESOURCE_DATA_ENTRY;
摘自:http://blog.sina.com.cn/s/blog_6cc1c52d0100t4wa.html
PE文件结构学习的更多相关文章
- PE文件结构详解(二)可执行文件头
在PE文件结构详解(一)基本概念里,解释了一些PE文件的一些基本概念,从这篇开始,将详细讲解PE文件中的重要结构. 了解一个文件的格式,最应该首先了解的就是这个文件的文件头的含义,因为几乎所有的文件格 ...
- 【PE结构】由浅入深PE基础学习-菜鸟手动查询导出表、相对虚拟地址(RVA)与文件偏移地址转换(FOA)
0 前言 此篇文章想写如何通过工具手查导出表.PE文件代码编程过程中的原理.文笔不是很好,内容也是查阅了很多的资料后整合出来的.希望借此加深对PE文件格式的理解,也希望可以对看雪论坛有所贡献.因为了解 ...
- PE文件结构及其加载机制
一.PE文件结构 PE即Portable Executable,是win32环境自身所带的执行体文件格式,其部分特性继承自Unix的COFF(Common Object File Format)文件格 ...
- 初识PE文件结构
前言 目前网络上有关PE文件结构说明的文章太多了,自己的这篇文章只是单纯的记录自己对PE文件结构的学习.理解和总结. 基础概念 PE(Portable Executable:可移植的执行体)是Win3 ...
- 再探.NET的PE文件结构(安全篇)
一.开篇 首先写在前面,这篇文章源于个人的研究和探索,由于.NET有自己的反射机制,可以清楚的将源码反射出来,这样你的软件就很容易被破解,当然这篇文章不会说怎么样保护你的软件不被破解,相反是借用一个软 ...
- PE文件学习系列二 DOS头分析
合肥程序员群:49313181. 合肥实名程序员群 :128131462 (不愿透露姓名和信息者勿加入)Q Q:408365330 E-Mail:egojit@qq.com PE文件结 ...
- PE文件结构详解(六)重定位
前面两篇 PE文件结构详解(四)PE导入表 和 PE文件结构详解(五)延迟导入表 介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里 ...
- PE文件结构详解(五)延迟导入表
PE文件结构详解(四)PE导入表讲 了一般的PE导入表,这次我们来看一下另外一种导入表:延迟导入(Delay Import).看名字就知道,这种导入机制导入其他DLL的时机比较“迟”,为什么要迟呢?因 ...
- PE文件结构详解(四)PE导入表
PE文件结构详解(二)可执行文件头的最后展示了一个数组,PE文件结构详解(三)PE导出表中解释了其中第一项的格式,本篇文章来揭示这个数组中的第二项:IMAGE_DIRECTORY_ENTRY_IMPO ...
随机推荐
- c语言入门教程 / c语言入门经典书籍
用C语言开始编写代码初级:C语言入门必备(以下两本书任选一本即可) C语言是作为从事实际编程工作的程序员的一种工具而出现的,本阶段的学习最主要的目的就是尽快掌握如何用c语言编写程序的技能.对c语言的数 ...
- JQuery遍历指定id的div name值的几种方法
JQuery遍历指定id的div name值的几种方法:方法一 $("#div1 :text").each(function () { var this_id = $(this). ...
- Mongodb的索引--学习笔记(未完)
全文索引 建立方法: --在articles集合的key字段上创建全文索引 db.articles.ensureIndex({key:"text"}) --在articles集合的 ...
- MVC的Filters(拦截过滤)的Error页面,支持Ajax报错
报错拦截过滤到error页面 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, A ...
- 用O(1)的时间复杂度,找到栈和队列中的最小(大)值
最近刷剑指offer,看到两道编程题,考察在O(1)的复杂度内,找出最值. 觉得很有意思,很有借鉴意义,故记录在此. 需要注意的是,这里所说的O(1) 有个前提, 就是已经通过某种容器的存储方式进行初 ...
- SQL Server 一些关键字详解(一)
1.CROSS APPLY 和OUTER APPLY MSDN解释如下(个人理解不是很清晰): 使用 APPLY 运算符可以为实现查询操作的外部表表达式返回的每个行调用表值函数.表值函数作为右输入,外 ...
- mq消息队列
rabbitmq学习9:使用spring-amqp发送消息及同步接收消息 通过对spring-amqp看重要类的认识,下面来通过spring-amqp的发送消息及同步接收消息是如何实现的.有兴趣的朋友 ...
- linux C socket
socket套接字和管道同样可以提供进程内通信.但套接字更胜一筹,不同的进程可以跨越不同的主机(说白了,支持网络通信).使用套接字的知名程序:telnet.rlogin.ftp等. 你需要知道的一些基 ...
- ED/EP系列3《基本指令》
Ø --APPLICATIONBLOCK(应用锁定): Ø --APPLICATION UNBLOCK(应用解锁): Ø --CARDBLOCK(卡片锁定): Ø --EXTERNAL AUTHENT ...
- poj 3641 Pseudoprime numbers
题目连接 http://poj.org/problem?id=3641 Pseudoprime numbers Description Fermat's theorem states that for ...