PE文件结构详解:
1.PE文件时windows下可执行文件的总称,常见的包括OCX,SYS,DLL,EXE等。
注意:一个文件是否是PE文件和后缀名无关。
2.PE结构:
|----------------|
| DOS头 |
|----------------|
| | |
| |PE签名 |
| NT头 |PE文件头|
| |PE可选头|
|----------------|
| 节表 |
|----------------|
| .rsrc资源节 |
| .text代码节 |
| .data数据节 |
|----------------|
详解: DOS头是用来兼容MS-DOS操作系统的,目的是 1.当这个文件在MS-DOS上运行时提示一段文字;2.指明NT头在文件中的位置
NT头包含windows PE文件的主要信息,其中包括一个‘PE’字样的签名,PE文件头(IMAGE_FILE_HEADER)和PE可选头(IMAGE_OPTIONAL_HEADER32)
节表:是PE文件后续节的描述,windows根据节表的描述加载每个节。
节:每个节实际上是一个容器,可以包含代码、数据等等,每个节可以有独立的内存权限,比如代码节默认有读/执行权限,节的名字和数量可以自己定义,未必是上图中的三个。
3.PE文件被加载到内存以后其占用的虚拟地址空间要比在硬盘上占用的空间大一些,这是因为各个节在硬盘上是连续的,而在内存中是按页对齐的
注意:因为存在这种对齐,所以在PE结构内部,表示某个位置的地址采用了两种方式:
1.物理地址:针对在硬盘上存储文件中的地址,表示距离文件头的偏移;
2.虚拟地址(RVA):针对加载到内存以后映象中的地址,表示相对内存映象头的偏移
3.VA是进程虚拟内存的绝对地址,RVA指的是从某个基准位置开始的相对地址。计算公式如下:RVA+ImageBase=VA //ImageBase基准地址
4.DOS头结构:
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; //一个WORD类型,值是一个常数0x5A4D,用文本编辑器查看该值位‘MZ’,可执行文件必须都是'MZ'开头
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; //为32位可执行文件扩展的域,用来表示DOS头之后的NT头相对文件起始地址的偏移
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
5.NT头结构:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //类似于DOS头中的e_magic,其高16位是0,低16是0x4550,用字符表示是'PE‘。
IMAGE_FILE_HEADER FileHeader; //是PE文件头,C语言的定义是这样的:
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
PE文件头:(定义了PE文件的一些基本信息和属性,这些属性会在PE加载器加载时用到)
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //该文件的运行平台,是x86、x64还是I64等等
WORD NumberOfSections; //该PE文件中有多少个节,也就是节表中的项数。
DWORD TimeDateStamp; //PE文件的创建时间,一般有连接器填写。
DWORD PointerToSymbolTable; //COFF文件符号表在文件中的偏移
DWORD NumberOfSymbols; //符号表的数量
WORD SizeOfOptionalHeader; //紧随其后的可选头的大小。
WORD Characteristics; //可执行文件的属性,
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
PE可选头:
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //表示可选头的类型。0x10b 32位PE可选头 0x20b 64位PE可选头
BYTE MajorLinkerVersion; //链接器的版本号
BYTE MinorLinkerVersion;
DWORD SizeOfCode; //代码段的长度,如果有多个代码段,则是代码段长度的总和。
DWORD SizeOfInitializedData; //初始化的数据长度。
DWORD SizeOfUninitializedData; //未初始化的数据长度
DWORD AddressOfEntryPoint; //程序入口的RVA,对于exe这个地址可以理解为WinMain的RVA
DWORD BaseOfCode; //代码段起始地址的RVA
DWORD BaseOfData; //数据段起始地址的RVA。
DWORD ImageBase; //映象基地址,这个基地址是建议,对于DLL来说,如果无法加载,系统会自动为其选择地址。
DWORD SectionAlignment; //节对齐,PE中的节被加载到内存时会按照这个域指定的值来对齐 比如0x1000
DWORD FileAlignment; //节在文件中按此值对齐,SectionAlignment必须大于或等于FileAlignment
WORD MajorOperatingSystemVersion; //所需操作系统的版本号
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion; //映象的版本号
WORD MinorImageVersion;
WORD MajorSubsystemVersion; //所需子系统版本号
WORD MinorSubsystemVersion;
DWORD Win32VersionValue; //保留,必须为0
DWORD SizeOfImage; //映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小
DWORD SizeOfHeaders; //所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的
DWORD CheckSum; //映象文件的校验和
WORD Subsystem; //运行该PE文件所需的子系统
WORD DllCharacteristics; //DLL的文件属性,只对DLL文件有效
DWORD SizeOfStackReserve; //运行时为每个线程栈保留内存的大小
DWORD SizeOfStackCommit; //运行时每个线程栈初始占用内存大小
DWORD SizeOfHeapReserve; //运行时为进程堆保留内存大小
DWORD SizeOfHeapCommit; //运行时进程堆初始占用内存大小
DWORD LoaderFlags; //保留,必须为0
DWORD NumberOfRvaAndSizes; //数据目录的项数,即下面这个数组的项数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录,这是一个数组
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
数据目录:(数组中的每一项对应一个特定的数据结构,包括导入表,导出表等等,具体哪一项表示什么内容请查看资料)
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 是一个RVA
DWORD Size; //是一个大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
注意:要从这个数组中获取具体某一项的值可以通过函数获取:
PVOID NTAPI RtlImageDirectoryEntryToData(
PVOID Base, //Base:模块基地址。也就是数组地址
BOOLEAN MappedAsImage, //MappedAsImage:是否映射为映象。
USHORT Directory, //Directory:数据目录项的索引。有定义好的具体的宏
PULONG Size //对应数据目录项的大小
);
//比如Directory=0;则表示导出表大小,如果一个模块导出了函数,那么这个函数会被记录在导出表中,这样通过GetProcAddress函数就能动态获取到函数的地址
//导出的时候,可以按照函数名导出(显示导出函数名字中有一些符号比如??@@),也可以按照序号导出,
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
节区结构:
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节区名称,如“.text” 不足8位则用0填充
union {
DWORD PhysicalAddress;//物理地址
DWORD VirtualSize;//实际的节区大小
} Misc;
DWORD VirtualAddress;//节区被装载到内存中的RVA地址
DWORD SizeOfRawData;// 在PE文件中对齐后的大小
DWORD PointerToRawData;// 在整个PE文件中的偏移量
DWORD PointerToRelocations;//节区在PE文件中的偏移
DWORD PointerToLinenumbers; // 行号表的偏移(供调试使用)
WORD NumberOfRelocations;// 在PE文件中使用,重定位项数目
WORD NumberOfLinenumbers;// 行号表中行号的数目
DWORD Characteristics;//节区的属性,如可读、可写、可执行等
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
PE文件结构详解
最新推荐文章于 2022-09-17 15:43:31 发布