PE结构详细解释 (棋幽版视频教程)
---------------------------------------
DOS STUB dos残余
----------------------------------------
PE头 (包括三个方面) PE头(PE文件标识符+文件头+可选头) 总大小是248字节
1、singature PE文件标识符 (4个字节) 50 45 00 00( PE00)
2、Image_File_Header FileHeader 文件头(20个字节)
a、machine intel 80386以上平台 值为4c 01
b、NumberOfSections 指出文件中所含有的区段(也叫区块,节)的数量
c、TimeDateStamp (DWORD类型) 文件创建时间日期(不重要)
省略两个不重要的
d、SizeOfOptionalHeader 可选头的大小如:(00E0为224个字节)这个值是固定的不好随便动的.
e、characteristics 文件标志(010F为exe文件,210E为DLL文件)
3、Image_Optional_Header OptionalHeader 可选头 (224 个字节)
//IMAGE_OPTIONAL_HEADER结构(可选映像头)
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; //幻数,一般为010BH
BYTE MajorLinkerVersion; //链接程序的主版本号
BYTE MinorLinkerVersion; //链接程序的次版本号
DWORD SizeOfCode; //代码段大小
DWORD SizeOfInitializedData; //已初始化数据块的大小
DWORD SizeOfUninitializedData; //未初始化数据库的大小
DWORD AddressOfEntryPoint; //程序开始执行的入口地址,这是一个RVA(相对虚拟地址) //很重要 例如notepad的入口地址为0000739D
DWORD BaseOfCode; //代码段的起始RVA
DWORD BaseOfData; //数据段的起始RVA
//
// NT additional fields.
//
DWORD ImageBase; //exe默认基址00400000,不过系统里面的记事本程序基址不同,是01000000.结合前面的入口地址(rva)0000739D,
所以进程中执行地址为01000000+0000739D=0100739D(注意两个概念执行地址和exe基地址) //DLL默认基址是10000000
exe基地址是pe源文件在进程中的地址.
//其实从字面上也好理解,Image是映射,当然是映射到内存了,映射基址也就是把PE源文件搬到这个基址的意思了.
//基址很重要啊!!!看到没,上面的三个RVA都是相对这个基址的.
DWORD SectionAlignment; //内存中块的对齐值(默认的块对齐值为1000H,4KB个字节)
DWORD FileAlignment;//文件中块的对齐值(默认值为200H字节,为了保证块总是从磁盘的扇区开始的)
WORD MajorOperatingSystemVersion;//要求操作系统的最低版本号的主版本号
WORD MinorOperatingSystemVersion;//要求操作系统的最低版本号的次版本号
WORD MajorImageVersion;//该可执行文件的主版本号
WORD MinorImageVersion;//该可执行文件的次版本号
WORD MajorSubsystemVersion;//要求最低之子系统版本的主版本号
WORD MinorSubsystemVersion;//要求最低之子系统版本的次版本号
DWORD Win32VersionValue;//保留字
DWORD SizeOfImage;//映像装入内存后的总尺寸
DWORD SizeOfHeaders;//部首及块表(也称节表)的大小
DWORD CheckSum;//CRC检验和
WORD Subsystem;//程序使用的用户接口子系统 //指文件在那种环境下运行
WORD DllCharacteristics;//DLLmain函数何时被调用,默认为0
DWORD SizeOfStackReserve;//初始化时堆栈大小
DWORD SizeOfStackCommit;//初始化时实际提交的堆栈大小
DWORD SizeOfHeapReserve;//初始化时保留的堆大小
DWORD SizeOfHeapCommit;//初始化时实际提交的对大小
DWORD LoaderFlags;//与调试有关,默认为0
DWORD NumberOfRvaAndSizes;//数据目录结构的数目 //很重要 一个数据目录表是8个字节.根据数据目录表的结构来算的. //可选头到这应该是96个字节,从幻数010B开始.
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表集合,如输入表目录(都有),
输出表目录(只有dll有)等
}
//补充一下:数据目录表结构体 DIRECTORY(目录)
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;//数据块的起始RVA 4字节
DWORD Size;//数据块的长度 4字节
} 8个字节
---------------------------------------------------------------------------------
节表组合 代码节 数据节等描述的结合 (这里注意一下节表和数据目录表是不同的,别搞混了)
Image_Section_Header 节表(区段表) 一个节表40个字节
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; 8个字节
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc; //虚拟大小或物理地址,取决于编译器.
DWORD VirtualAddress;//该块的RVA //在内存中的偏移地址,也称虚拟地址,用OD载入后,在OD中计算
DWORD SizeOfRawData;//在文件中对齐后的尺寸 // 物理大小 也称在文件中的大小,用C32AM来计算
DWORD PointerToRawData;//在文件中偏移 //物理偏移 也称在文件中的偏移 ,用C32AM来计算 在 以上三个很重要(这几个地址是在第六课理解的)
DWORD PointerToRelocations;//重定位偏移
DWORD PointerToLinenumbers;//行号表的偏移
WORD NumberOfRelocations;//重定位项数目
WORD NumberOfLinenumbers;//行号表中行号的数目
DWORD Characteristics;//块属性 //块属性也很重要,比如可以设置读取或写入等等属性
}
------------------------------------------------------------------------------------------------------------------
文件映射到内存。(第五课)
在可执行程序运行之前,PE加载器将把PE文件加载到进程空间的内存中去,并且初始化每个段实体。那么加载到内存中的哪个地址去呢?这将由IMAGE_OPTIONAL_HEADER32结构的成员10的值指出加载的起始地址(又叫基地址)。这个值通常是“00400000”, 那么PE文件的首地址“00000”就被映射到内存地址“00400000”处,那么相对于文件偏移10个字节的地址为“00010”,被映射
到内存后的偏移也应该是10个字节,映射后的地址应该为“00400010”。
RVA是相对虚拟地址(Relative Virtual Address)的缩写,顾名思义,它是一个“相对”地址,也可以说是“偏移量”,PE文件的各种数据结构中涉及到地址的字段大部分都是以RVA表示的。
准 确地说,RVA就是当PE文件被装载到内存中后,某个数据的位置相对于文件头的偏移量。举个例子,如果Windows装载器将一个PE文件装入 00400000h处的内存中,而某个节中的某个数据被装入0040xxxxh处,那么这个数据的RVA就是(0040xxxxh- 00400000h)=xxxxh,反过来说,将RVA的值加上文件被装载的基地址,就可以找到数据在内存中的实际地址。
PE文件中出现RVA的 概念是因为PE的内存映像和磁盘文件映像是不同的,同一数据相对于文件头的偏移量在内存中和在磁盘文件中可能是不同的,为了提高效率,PE文件头中使用的 都是内存映像中的偏移量,也就是RVA。从图17.3中也可以得到另一个结论,那就是RVA仅仅是对于处于节中的数据而言的,对于文件头和节表来说无所谓 RVA和文件偏移,因为它们在被映射到内存中后不管是大小还是偏移都不会有任何改变。
2、汇编中虚拟地址(VRA)与文件偏移地址(FileOffset)的相互转换:
+---------+---------+---------+---------+---------+---------+
| 段名称 虚拟地址 虚拟大小 物理地址 物理大小 标志 |
+---------+---------+---------+---------+---------+---------+
| Name VOffset VSize ROffset RSize Flags | //raw指代文件,也可用物理表示
+---------+---------+---------+---------+---------+---------+ //凡是涉及虚拟的,就是指内存
| .text 00001000 00000092 00000400 00000200 60000020|
| .rdata 00002000 000000F6 00000600 00000200 40000040|
| .data 00003000 0000018E 00000800 00000200 C0000040|
| .rsrc 00004000 000003A0 00000A00 00000400 C0000040|
+---------+---------+---------+---------+---------+---------+
文件虚拟偏移地址和文件物理偏移地址的计算公式如下:
>>>>>>>VaToFileOffset( 虚拟地址转文件偏移地址)
如VA = 00401000 (虚拟地址)
ImageBase = 00400000 (基地址)
VRk = VOffset - ROffset = 00001000 - 00000400 = C00 (得出文件虚拟地址和文件物理址之间的VRk值)
FileOffset = VA - ImageBase - VRk = 00401000 - 00400000 - C00 = 400(文件物理地址的偏移地址)
如VA = 00401325,则:
FileOffset = VA - ImageBase - VRk = 00401325 - 00400000 - C00 = 725
>>>>>>FileOffsetToVa( 文件偏移地址转虚拟地址)
如FileOffset = 435(文件偏移地址)
VA = FileOffset + ImageBase + VRk = 435 + 00400000 + C00 = 00401035(虚拟地址)
---------------------------------------------------------------------------------------------------------------------------
把PE文件载入或映射到内存后,所有的头和节表(如notepad是592个字节)放到如00400000到00401000这4k字节中.592字节以外的剩下的用00填充.
(notepad放到01000000到01001000中)
文件对齐值 (第六课)
内存中 为1000h,4k个字节
文件中 为200h,为512个字节
PE文件中的节无论是在磁盘中或内存映像中都是按照一定单位进行对齐的。
那么对齐的具体情况是什么样的呢?
假如在内存中的对齐单位是4 Kb,那么Windows是不是将PE文件中节的有效数据(没有进行任何对齐时的数据),逐个填满4 Kb,然后再将最后那个还没填满的4 Kb用零补足呢?也就是说Windows为进行对齐而补上的零不会超过4 Kb!
-------------------------------------------------------------------------------------------------------------