PE文件详解------PE文件结构剖析
来源:互联网 发布:linux redis 批量删除 编辑:程序博客网 时间:2024/06/11 09:57
一.PE文件结构纵览
PE文件的结构如上图所示,由低地址到高地址分别为:Dos头,PE头,块表,块,调试信息。其中真正的PE文件头是位于Dos头的后面的部分。
上图为利用PE工具打开的一个可执行文件在磁盘中的映射,这就是PE文件的内部信息。
可以看到这个文件的钱两个字节为”4D 5A”,表示的就是Dos头的第一个字段的额信息,标示Dos的头部。在Dos头结构的最后一个字段是一个指针,这个指针指向PE头。在上图中可以看到00 00 00 60表示的就是这个指针的值,它指向这个PE文件的真正PE头。根据这个地址可以找到PE头开始的地方,45 50表示的就是PE头结构的而第一个字段,标示的就是PE头。可以看到,PE文件的结构十分分明,没有一块多余的地方,各个部分排列紧凑,井井有条。我们获取PE文件的信息就是通过读取这个2进制文件。
二.Dos 头(Dos Header)
Dos头中的信息存储在IMAGE_DOS_HEADER中。这个结构的定义为:
IMAGE_DOS_HEADER STRUCT
{
+0hWORDe_magic //Magic DOS signature MZ(4Dh 5Ah) DOS 可执行文件标记
+2h WORD e_cblp //Bytes on last page of file
+4hWORD e_cp //Pages in file
+6hWORD e_crlc //Relocations
+8hWORD e_cparhdr //Size of header in paragraphs
+0ahWORD e_minalloc //Minimun extra paragraphs needs
+0chWORD e_maxalloc //Maximun extra paragraphs needs
+0ehWORD e_ss //intial(relative)SS value DOS 代码的初始化堆栈SS
+10hWORD e_sp //intial SP value DOS 代码的初始化堆栈指针SP
+12hWORD e_csum //Checksum
+14hWORD e_ip // intial IP value DOS 代码的初始化指令入口[指针IP]
+16hWORD e_cs //intial(relative)CS value DOS 代码的初始堆栈入口
+18hWORD e_lfarlc //File Address of relocation table
+1ahWORD e_ovno // Overlay number
+1chWORD e_res[4] //Reserved words
+24hWORD e_oemid // OEM identifier(for e_oeminfo)
+26hWORD e_oeminfo // OEM information;e_oemid specific
+29hWORD e_res2[10] // Reserved words
+3chDWORD e_lfanew //Offset to start of PE header 指向PE 文件头
} IMAGE_DOS_HEADER ENDS
在Image_Dos_Header 中有几个成员是很重要的,第一个magic,这个字段是来标示是否为Dos 程序的,如果是的话那么就会有MZ 的标记,16 进制为4Dh 5Ah,一个字母占一个字节,一共是两个字节。还有一个是最后一个字段,e_lfanew,这是一个指向PE 文件头的指针,指出了PE 文件头在PE 文件中的偏移。如果这个值为0,那么说明这个exe 文件是一个Dos 程序,windows 会启动Dos 子系统来运行它。
三.Dos Stub
Dos Stub部分其实就是一段windows下的一段代码,当windows程序在Dos系统下运行的时候,那么系统就会返回一段信息,表示这个程序是一个windows下的可执行文件,不能在Dos系统下运行。
四.PE头(PE Header )
这部分是PE文件真正的PE头,它存储在一个结构体中,它的类型为IMAGE_NT_HEADER。
1.PE头的结构 IMAGE_NT_HEADER
PE头实际上是一个IMAGE_NT_HEADER结构,它的定义为:
IMAGE_NT_HEADERS STRUCT{
+0hDWORDSignature //
+4h IMAGE_FILE_HEADER FileHeader //
+18hIMAGE_OPTIONAL_HEADER32OptionalHeader //
} IMAGE_NT_HEADERS ENDS
紧接着Image_Dos_Header 的结构是Image_NT_Header,这是PE 真正的头文件。这个结构包含了三个字段,第一个是Siganture,第二个是一个结构Image_FILE_Header,第三个是Image_Optional_Header。
在一个有效的PE 文件里,Signature 字段被设置为00004550h, ASCII 码字符是“PE00”。标志这PE 文件头的开始。“PE00” 字符串是PE 文件头的开始, DOS 头部的e_lfanew 字段正是指向这里。
Image_FILE_Header 结构为PE 映像头,而Image_Optional_Header 是PE 扩展头部。
2.PE文件映像头结构 IMAGE_FILE_HEADER
它的定义为:
typedef struct _IMAGE_FILE_HEADER
{
+04h WORD Machine; // 运行平台
+06h WORD NumberOfSections; // 文件的区块数目
+08h DWORDTimeDateStamp; // 文件创建日期和时间
+0Ch DWORD PointerToSymbolTable; // 指向符号表(主要用于调试)
+10hDWORD NumberOfSymbols; // 符号表中符号个数(同上)
+14h WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER32 结构大小
+16h WORD Characteristics; // 文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
以下为对IMAGE_FILE_HEADER的部分数据成员做出解释:
Machine:可执行文件的目标CPU 类型。
NumberOfSection: 区块的数目。(注:区块表是紧跟在IMAGE_NT_HEADERS 后边的)
TimeDataStamp: 表明文件是何时被创建的。
这个值是自1970 年1 月1 日以来用格林威治时间(GMT)计算的秒数,这个值是比文件系统( FILESYSTEM ) 的日期时间更加精确的指示器。如何将这个值翻译请看:
PointerToSymbolTable: COFF 符号表的文件偏移位置,现在基本没用了。
NumberOfSymbols: 如果有COFF 符号表,它代表其中的符号数目,COFF 符号是一
个大小固定的结构,如果想找到COFF 符号表的结束位置,则需要这个变量。
SizeOfOptionalHeader: 紧跟着IMAGE_FILE_HEADER 后边的数据结构(IMAGE_OPTIONAL_HEADER)的大小。(对于32 位PE 文件,这个值通常是00E0h;对于64 位PE32+文件,这个值是00F0h )。
Characteristics: 文件属性,有选择的通过几个值可以运算得到。( 这些标志的有效值是定义于winnt.h 内的IMAGE_FILE_** 的值,具体含义见下表。普通的EXE 文件这个字段
的值一般是0100h,DLL 文件这个字段的值一般是210Eh。)
3.可选的PE头结构 IMAGE_OPTIONAL_HEADER
它的定义为:
typedef struct _IMAGE_OPTIONAL_HEADER
{
//
// Standard fields.
//
+18h WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
+1Ah BYTE MajorLinkerVersion; // 链接程序的主版本号
+1Bh BYTE MinorLinkerVersion; // 链接程序的次版本号
+1Ch DWORD SizeOfCode; // 所有含代码的节的总大小
+20h DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小
+24h DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小
+28h DWORD AddressOfEntryPoint; // 程序执行入口RVA
+2Ch DWORD BaseOfCode; // 代码的区块的起始RVA
+30h DWORD BaseOfData; // 数据的区块的起始RVA
//
// NT additional fields. 以下是属于NT 结构增加的领域。
//
+34h DWORD ImageBase; // 程序的首选装载地址
+38h DWORD SectionAlignment; // 内存中的区块的对齐大小
+3Ch DWORD FileAlignment; // 文件中的区块的对齐大小
+40h WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号
+42h WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号
+44h WORD MajorImageVersion; // 可运行于操作系统的主版本号
+46h WORD MinorImageVersion; // 可运行于操作系统的次版本号
+48h WORD MajorSubsystemVersion; // 要求最低子系统版本的主版本号
+4Ah WORD MinorSubsystemVersion; // 要求最低子系统版本的次版本号
+4Ch DWORD Win32VersionValue; // 莫须有字段,不被病毒利用的话一般为0
+50h DWORD SizeOfImage; // 映像装入内存后的总尺寸
+54h DWORD SizeOfHeaders; // 所有头+ 区块表的尺寸大小
+58h DWORD CheckSum; // 映像的校检和
+5Ch WORD Subsystem; // 可执行文件期望的子系统+5Eh WORD DllCharacteristics; // D
llMain()函数何时被调用,默认为0
+60h DWORD SizeOfStackReserve; // 初始化时的栈大小
+64h DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小
+68h DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
+6Ch DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小
+70h DWORD LoaderFlags; // 与调试有关,默认为0
+74h DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT
发布以来// 一直是16
+78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENT
RIES];
// 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
这其中的大部分结构都不重要,不过有几个是很重要的。
(1).AddressOfEntryPoint 字段
指出文件被执行时的入口地址,这是一个RVA 地址(RVA 的含义在下一节中详细介绍)。如
果在一个可执行文件上附加了一段代码并想让这段代码首先被执行,那么只需要将这个入口
地址指向附加的代码就可以了。
(2). ImageBase 字段
指出文件的优先装入地址。也就是说当文件被执行时,如果可能的话,Windows 优先将文件装入到由ImageBase 字段指定的地址中,只有指定的地址已经被**模块使用时,文件才被装入到**地址中。链接器产生可执行文件的时候对应这个地址来生成机器码,所以当文件被装入这个地址时不需要进行重定位操作,装入的速度最快,如果文件被装载到**地址的话,将不得不进行重定位操作,这样就要慢一点。对于EXE 文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被**模块占据,所以EXE 总是能够按照这个地址装入,这也意味着EXE 文件不再需要重定位信息。对于DLL 文件来说,由于多个DLL 文件全部使用宿主EXE 文件的地址空间,不能保证优先装入地址没有被**的DLL 使用,所以DLL 文件中必须包含重定位信息以防万一。因此,在前面介绍的IMAGE_FILE_HEADER 结构的Characteristics 字段中,DLL 文件对应的IMAGE_FILE_RELOCS_STRIPPED 位总是为0,而EXE 文件的这个标志位总是为1。在链接的时候,可以通过对link.exe 指定/base:address 选项来自定义优先装入地址,如果不指定这个选项的话,一般EXE 文件的默认优先装入地址被定为00400000h,而DLL 文件的默认优先装入地址被定为10000000h。
(3). SectionAlignment 字段和FileAlignment 字段
SectionAlignment 字段指定了节被装入内存后的对齐单位。也就是说,每个节被装入的地址必定是本字段指定数值的整数倍。而FileAlignment 字段指定了节存储在磁盘文件中时的对齐单位。
(4).Subsystem 字段
指定使用界面的子系统,在windows下可选的子系统为CUI和GUI。这个字段决定了系统如何为程序建立初始的界面,链接时的/subsystem:**选项指定的就是这个字段的值,在前面章节的编程中我们早已知道:如果将子系统指定为Windows CUI,那么系统会自动为程序建立一个控制台窗口,而指定为Windows GUI 的话,窗口必须由程序自己建立。
4.数据目录(Data Directory)
在结构IMAGE_NT_HEADER 中有一个结构IMAGE_OPTINAL_HEADER 结构,在IMAGE_OPTINAL_HEADER 中又有一个字段,这个字段是一个结构数组IMAGE_DATA_DIREECTORY[]。这个结构数组中的数据的类型全部都是IMAGE_DATA_HEADER 结构,这个结构中有两个成员,一个是RVA(相对虚拟地址),一个是Size(大小)。它的定义如下:
Typedefe IMAGE_DATA_DIRECTORT
{
DWORD VirtualAddress ;
DWORD Size ;
} IMAGE_DATA_DIRECTORT ;
在windows 系统撞在PE 可执行文件时,往往需要很快摘到一些装载需要的数据结构,比如导入表,导出表,资源,重定位表等等。这些常用的数据结构的位置和长度都被保存在IMAGE_DATA_HEADER 结构里。在这个数组里面,每一个元素都对应一个一个包含一定信息的表。比如数组的第一个元素就是记录的是导出表的地址的长度。
5.导入表(Import table )
在代码分析或编程中经常遇到“输入函数(Import Functions,也称导入函数)”的概念。这里我们就来解释下,输入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于相关的DLL 文件中,在调用者程序中只保留相关的函数信息(如函数名、DLL 文件名等)就可以。
对于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后,Windows 加载器才将相关DLL 装入,并将调用输入函数的指令和函数实际所处的地址联系起来。这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“输入表”来完成的,输入表中保存的正是函数名和其驻留的DLL 名等。
在PE 文件头的IMAGE_OPTIONAL_HEADER 结构中的DataDirectory(数据目录表) 的第二个成员就是指向输入表的。而输入表是以一个IMAGE_IMPORT_DESCRIPTOR(简称IID)
的数组开始。每个被PE 文件链接进来的DLL 文件都分别对应一个IID 数组结构。在这个
IID 数组中,并没有指出有多少个项(就是没有明确指明有多少个链接文件),但它最后是以
一个全为NULL(0) 的IID 作为结束的标志。
IMAGE_IMPORT_DESCRIPTOR STRUCT
{
union
Characteristics DWORD ?
OriginalFirstThunk DWORD ?
ends
TimeDateStamp DWORD ?
ForwarderChain DWORD ?
Name DWORD ?
FirstThunk DWORD ?
}
IMAGE_IMPORT_DESCRIPTOR ENDS
五.块表(Section Table)
紧接着PE文件的头部就是section table(段表)了,它记录着pe文件中每一个区(段)的信息。块在映像中是按起始地址排(RVA)列的。它的定义如下:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节表名称,如“.text”
//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;
1.VirtualSize:该块的真实长度。和SizeOfRawData不一样,是块对齐前的长度。
2. VirtualAddress:该块装载到内存中的RVA。这个地址是按照内存页对齐的,它的数值总是SectionAlignment的整数倍。
3. SizeOfRawData:该块在磁盘中所占的大小。在可执行文件中,该字段包含经过FileAlignment调整后的块的长度。
4.PointerToRawData:该块在磁盘中的偏移。程序经过编译或汇编后生成原始数据,这个字段用于给原始数据在文件中的偏移
5.Characteristics:块属性。该字段是一组指出块属性(如代码/数据/可读/可写等)的标志。
六.块(Section)结构
1.text块
.text是在编译或者汇编结束时产生的一种块。它的内容全是指令代码。PE文件运行在32方式下,不受16位的约束,所以没有理由把代码放到不同的sections中。链接器把所有目标文件的.text块链接成一个大块的.text中。
2.data块
如同.text是默认的代码块一样,.data是初始化的数据块。这些数据包括编译时被初始化的global和static变量,也包括字符串。链接器把obj及lib文件的.data结合成一个大的.data。Local数据可以放在一个线性的堆栈中,不占.data和.bbs的空间。
3.idata
.idata包含其他外来的DLL的函数及数据信息,即输入表。该功能与NE文件的模块引用表类似,关键的差异在于PE文件中的每一个输入函数都明确地列于该块中。要在NE格式中找到相同的信息,必须从各段的重定位数据中查找。
4 .rsrc
.rsrc包含模块的全部资源数据,如图表,菜单,位图等。
5.reloc
.reloc保存基地址重定位表。当装载程序不能按链接器所指定的地址装载文件时,需要对指令或已初始化的变量进行调整,基址重定位表包含调整所需的数据,如果装载程序能正常装载文件,它就忽略.reloc中的重定位数据。
6.edata
.edata是该PE文件的输出表,以供其他模块引用。PE格式文件没有必要输出一个函数,所以通常只是在DLL文件中才能看到.edata块。
7.rdata
.rdata块通常是在.data或.bbs中间,但程序很少用到该块中的数据。
8.tls
TLS的意思是”线程局部存储器。
其他常见的块:.debug$s , .bbs等。
关于PE文件的详解,大家看以参考鱼C论坛小甲鱼的加解密系统篇的视频,讲的很赞。
- PE文件详解------PE文件结构剖析
- PE文件结构剖析
- PE文件结构剖析
- PE文件结构详解
- PE文件结构详解
- PE文件结构详解
- PE文件结构详解
- PE文件结构详解-PE导入表
- PE文件结构详解--PE导出表
- PE文件结构详解<一>
- PE文件结构详解<二>
- PE文件结构详解<三>
- PE文件结构详解--基本概念
- 深入剖析PE文件
- 深入剖析PE文件
- 深入剖析PE文件
- 深入剖析PE文件
- 深入剖析PE文件
- shell 的cut 命令用法
- 黑马程序员_java编程基础第3天1-5 循环结构while;do while;for
- 小思考
- 18.C++构造函数的重载
- memcached 高可用工具 memcached-ha
- PE文件详解------PE文件结构剖析
- 转)计算机领域的顶级会议和期刊
- mvc 从后台得到前台元素值的方法
- java对byte数组解压缩(zip,gzip,bzip2,jzlib)
- ubuntu安装subversion
- 使用 Storyboard Segue 实作 UIViewController 的切换
- [MySQL] 索引与性能(4)- 排序
- linux系统服务的基础知识
- SGU105—— Div 3