PE文件解析(一) 您所在的位置:网站首页 什么是PE文件以及PE文件执行基本过程可以配图和讲解 PE文件解析(一)

PE文件解析(一)

2024-04-28 11:11| 来源: 网络整理| 查看: 265

基本概念 什么是PE文件 PE文件的全程:Portable Executable,即可移植的可执行文件 常见的PE文件:EXE文件、DLL文件、OCX文件、SYS文件、COM文件 PE文件通常是指32位的,而64位的PE文件通常称为PE32+、PE+、PE64 文件偏移地址、虚拟地址与相对虚拟地址 文件偏移地址:PE文件存储在磁盘中时,某个数据的位置相对于文件头部的偏移量,通常将其称为文件偏移地址(File Offset Address)或物理地址(RAW Offset) 虚拟地址:在Windows系统中,PE文件会被系统加载器映射到内存中,而每个PE文件都有其自己的独立的虚拟空间,这个虚拟空间的内存地址就被称为虚拟地址(Virtual Address) 相对虚拟地址:当PE文件映射到内存之后,某个数据相对于文件载入点地址(即基地址,ImageBase)的偏移量,通常称其为相对虚拟地址(Relative Virtual Address),虚拟地址与相对虚拟地址存在如下关系:虚拟地址(VA) = 基地址(ImageBase) + 相对虚拟地址(RVA) PE结构图

PE文件框架结构

PE文件的详细结构

PE文件磁盘结构与内存结构(对齐原因)

PE Headers解析

首先需要明确的是,严格意义上的PE文件头是指IMAGE_NT_HEADERS,但为了方便解析,此处将

IMAGE_DOS_HEADER(DOS头) IMAGE_NT_HEADERS(NT头) IMAGE_FILE_HEADER(映像文件头) IMAGE_OPTIONAL_HEADER(可选映像头) IMAGE_SECTION_HEADER(区块表)

这五个部分都视作PE的头部部分一并进行解析

IMAGE_DOS_HEADER

MS-DOS头部,每个PE文件都是以一个DPS程序开始的,且DOS可以识别出一个文件是不是一个有效的执行体,若其首部的e_magic被置为0x5A4D(即ASCII的 "MZ",这个值在winnt.h中有一个#define,名为IMAGE_DOS_SIGNATURE),那么该文件就是一个DOS可执行文件

123456789101112131415161718192021typedef struct _IMAGE_DOS_HEADER { WORD e_magic; // DOS可执行文件标记 "MZ" WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; // DOS代码入口IP WORD e_cs; // DOS代码入口CS WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; LONG e_lfanew; // 偏移地址,指向IMAGE_NT_HEADERS,"PE00"(0x00004550) } IMAGE_DOS_HEADER

其中比较重要的两个字段分别是e_magic与e_lfanew,前者的作用已经解释过,而后者e_lfanew是真正的PE文件头IMAGE_NT_HEADERS的相对偏移(既是FOA也是RVA)

用十六进制编辑器打开exe文件可以发现,起始位置的"MZ" 标记就是e_magic字段的值,而e_lfanew的值为"0x000000F0",在相对文件起始位置0x000000F0的位置我们可以找到真正的PE文件头标记"PE00"

我们可以观察到在e_lfanew和真正的PE头之间还有一些数据,这部分数据被称为DOS stub(即DOS块),DOS stub实际上是一个有效的exe,在不支持PE文件格式的操作系统中,它将小时一个错误提示,即"This program cannot be run in DOS mode",DOS stub的数据大多由编译器自动生成,我们也可根据自己的需要修改其中的内容,我们将IMAGE_DOS_HEADER与DOS stub合称为DOS文件头

IMAGE_NT_HEADERS

紧跟着DOS stub de就是真正的PE文件头了,这部分也被称为NT映像头,在一个有效PE文件中,其Signature字段被置为0x00004550(即ASCII的"PE00",这个值在winnt.h中也有一个#define,名为IMAGE_NT_SIGNATURE),而紧跟在Signature字段之后的就是IMAGE_FILE_HEADER映像文件头,在此之后紧跟的是IMAGE_OPTIONAL_HEADER可选映像头

12345typedef struct _IMAGE_NT_HEADERS { DWORD Signature; // PE文件标识 IMAGE_FILE_HEADER FileHeader; // 偏移地址,指向 IMAGE_FILE_HEADER IMAGE_OPTIONAL_HEADER32 OptionalHeader; // 偏移地址,指向 IMAGE_FILE_HEADER} IMAGE_NT_HEADERS32

在十六进制编辑器中,NT影响头的结构如下图所示,首个字段即为"PE00"标记,紧跟其后红色框所示部分就是映像文件头,紧跟映像文件头之后的蓝色框所示的部分就是可选映像头

IMAGE_FILE_HEADER

映像文件头中包含PE文件的一些基本信息,大小为20字节,其中较为重要的两个字段为NumberOfSections字段与SizeOfOptionalHeader字段,前者指出了区块Section的数量(同时也指明了IMAGE_SECTION_HEADER区块表的数量,因为每一个区块表记录了对应区块的相关信息),后者指出了IMAGE_OPTIONAL_HEADER可选映像头的大小

123456789typedef struct _IMAGE_FILE_HEADER { WORD Machine; // 运行平台 WORD NumberOfSections; // 区块数 DWORD TimeDateStamp; // 文件创建的日期和时间 DWORD PointerToSymbolTable; // 指向符号表(用于调试) DWORD NumberOfSymbols; // 符号表中的符号的个数(用于调试) WORD SizeOfOptionalHeader; // 可选映像头的大小 WORD Characteristics; // 文件属性} IMAGE_FILE_HEADER

这里对字段进行详细的解释: 1. Machine:可执行文件的目标CPU类型,因为不同平台上指令集不同,因此需要该字段标识运行的平台,如Inter i386及其之后的处理器,该字段的值都为0x14C 2. NumberOfSections:区块数 3. TimeDateStamp:文件创建的时间,将该值翻译为易读字符串需要使用_ctime函数 4. PointerToSymbolTable:COFF符号表的文件偏移位置(FOA),现较为少见 5. NumberOfSymbols:如果有文件符号表,其指出了文件符号表中符号的数目 6. SizeOfOptionalHeader:可选映像头的大小,其大小通常依赖于文件是32位还是64位的,若是32位文件,这个值默认为0x00E0,若是64位文件,这个值默认为0x00F0,这表示了选映像头大小的最小值,因此该值是可以修改的 7. Characteristics:文件属性,通过选择几个有效值计算得到,有效值在winnt.h定义

123456789101112131415#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // 不存在重定位信息#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // 文件可执行 若为0,通常是链接时出问题#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // 行号信息被移除#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // 符号信息被移除#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Aggressively trim working set#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // 应用程序可以处理超过2GB的地址,因为大部分数据库服务器需要很大的内存,而NT仅提供2GB给应用程序,因此从NT SP3开始,可以通过设置此参数,使应用程序分配2 ~ 3GB区域的地址(此部分原本为系统内存区)#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // 处理器的低位字节是相反的#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 目标平台为32为机器#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // .DBG文件的调试信息被移除#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // 如果映像文件在可移动介质中,则先复制到交换文件中再运行#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // 如果映像文件在网络中,则先复制到交换文件后再运行#define IMAGE_FILE_SYSTEM 0x1000 // 系统文件#define IMAGE_FILE_DLL 0x2000 // DLL文件#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // 文件只能运行在单处理上#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // 处理器的高位字节是相反的 IMAGE_OPTIONAL_HEADER

虽然称为可选映像头,但该结构是必不可少的,其中定义了更多的数据 123456789101112131415161718192021222324252627282930313233typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; // 标志字 BYTE MajorLinkerVersion; // 链接器主版本号 BYTE MinorLinkerVersion; // 连接器次版本号 DWORD SizeOfCode; // 所有含有代码的区块的大小 DWORD SizeOfInitializedData; // 所有初始化数据区块大小 DWORD SizeOfUninitializedData; // 所有未初始化数据区块大小 DWORD AddressOfEntryPoint; // 程序执行入口RVA DWORD BaseOfCode; // 代码区块起始RVA DWORD BaseOfData; // 数据区块起始RVA DWORD ImageBase; // 程序默认载入基地址 DWORD SectionAlignment; // 内存中块的对齐值 DWORD FileAlignment; // 磁盘文件中块的对齐值 WORD MajorOperatingSystemVersion; // 操作系统主版本号 WORD MinorOperatingSystemVersion; // 操作系统次版本号 WORD MajorImageVersion; // 用户自定义主版本号 WORD MinorImageVersion; // 用户自定义次版本号 WORD MajorSubsystemVersion; // 所需子系统主版本号 WORD MinorSubsystemVersion; // 所需子系统此版本号 DWORD Win32VersionValue; // 保留,通常设置为0 DWORD SizeOfImage; // 映像载入内存后的总大小 DWORD SizeOfHeaders; // DOS头、PE文件头、区块表的总大小 DWORD CheckSum; // 映像校验和 WORD Subsystem; // 文件子系统 WORD DllCharacteristics; // 显示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

这里对一些较为关键的字段进行详细的解释: 1. Magic:标志字,ROM映像为0x107,32位可执行映像为0x010B,64位可执行映像位0x020B 2. SizeOfCode:所有含有IMAGE_SCN_CNT_CODE属性的区块的总大小,必须是FileAlignment的整数倍,由编译器填写,通常情况下,大多数文件只有一个Code块,所以该字段与.text块的大小匹配 3. SizeOfUninitializedData:所有未初始化数据区块大小,装载程序需要在虚拟地址空间中位这些数据分配空间,这些块在磁盘文件中不占空间,在程序开始运行时没有指定值,未初始化数据通常在.bss块中 4. AddressOfEntryPoint:程序执行入口RVA。对于DLL,这个入口点在进程初始化和关闭时与线程创建和销毁时被调用,在大多数可执行文件中,这个地址不直接指向Main、WinMain或DllMain,而是指向运行时的库代码,并由它来调用上述函数 5. ImageBase:程序默认载入基地址,如果PE文件在这个地址载入,加载器将会跳过应用基址重定位的步骤 6. SectionAlignment:载入内存时,内存中块的对齐值,也就是说每个区块被载入的地址必定是本字段指定数值的整数倍,默认的对齐尺寸是目标CPU的页尺寸(通常是0x10000,也就是4KB) 7. FileAlignment:磁盘文件中块的对齐值,区块在磁盘文件中存储的首地址必定是本字段指定数值的整数倍,对于x86可执行文件,这个值常为0x200或0x1000,这是为了保证块总是从磁盘的扇区开始,该值必须是2的幂 8. SizeOfImage:映像载入内存后的总大小,即从ImageBase到最后一个块结束,且按照SectionAlignment对齐的大小 9. SizeOfHeaders:DOS头、PE文件头、区块表的总大小,按FileAlignment对齐 10. CheckSum:映像校验和,CheckSumMappedFile函数可以计算该值,通常情况下,普通的EXE文件该值为0,但内核模式的驱动程序和系统DLL必须有一个校验和 11. NumberOfRvaAndSizes:数据目录项的数量,该值至今一直为16 12. DataDirectory[16]:数据目录数组,由数个相同的IMAGE_DATA_DIRECTORY结构组成,其具体的结构如下

1234typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // 数据块的RVA DWORD Size; // 数据块的大小} IMAGE_DATA_DIRECTORY

数据目录表成员的结构如下所示

序号 表名 结构 0 Export Table IMAGE_DIRECTORY_ENTRY_EXPORT 1 Import Table IMAGE_DIRECTORY_ENTRY_IMPORT 2 Resources Table IMAGE_DIRECTORY_ENTRY_RESOURCE 3 Exception Table IMAGE_DIRECTORY_ENTRY_EXCEPTION 4 Security Table IMAGE_DIRECTORY_ENTRY_SECURITY 5 Base Relocation Table IMAGE_DIRECTORY_ENTRY_BASERELOC 6 Debug IMAGE_DIRECTORY_ENTRY_DEBUG 7 Copyright IMAGE_DIRECTORY_ENTRY_COPYRIGHT 8 Global Ptr IMAGE_DIRECTORY_ENTRY_GLOBALPTR 9 Thread Local Storage (TLS) IMAGE_DIRECTORY_ENTRY_TLS 10 Load Configuration IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 11 Bound Import IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 12 Import Address Table (IAT) IMAGE_DIRECTORY_ENTRY_IAT 13 Delay Import IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 14 COM Descriptor IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 15 保留,必须为0 - IMAGE_SECTION_HEADER

区块表中记录了区块的具体信息,每个区块表分别指向了不同的区块实体,紧跟在 IMAGE_OPTIONAL_HEADER 之后

123456789101112131415typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 8字节大小的块名 union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; // 实际被使用的区块的大小,未对齐 DWORD VirtualAddress; // 该块装载到内存中的RVA DWORD SizeOfRawData; // 在磁盘中区块的大小,已对齐 DWORD PointerToRawData; // 该块在磁盘中的偏移FOA DWORD PointerToRelocations; // 在EXE中无意义,在OBJ文件中表示本块重定位信息表的偏移 DWORD PointerToLinenumbers; // 调试信息,行号表在文件中的偏移 WORD NumberOfRelocations; // 在EXE中无意义,在OBJ文件中表示本块在重定位表中重定位数量 WORD NumberOfLinenumbers; // 该块在行号表中的行号数量 DWORD Characteristics; // 块属性} IMAGE_SECTION_HEADER

块属性中的一些重要字段值如下所示

12345678#define IMAGE_SCN_CNT_CODE 0x00000020 // 包含代码,通常与0x10000000一起设置#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 // 包含已初始化数据#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 // 包含未初始化数据#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // 该块可被丢弃,因为它一旦被载入,进程就不再需要它了,常见的可丢弃块是.reloc(重定位块)#define IMAGE_SCN_MEM_SHARED 0x10000000 // 该块为共享块#define IMAGE_SCN_MEM_EXECUTE 0x20000000 // 该块可执行,通常当0x00000020标志被设置时,该标志也被设置#define IMAGE_SCN_MEM_READ 0x40000000 // 该块可读,可执行文件中总是设置该标志#define IMAGE_SCN_MEM_WRITE 0x80000000 // 该块可写,若PE文件中没有设置该标志,装载程序就会将内存映像页标记为可读或可执行

在十六进制编辑器中的区块表信息如下图所示,可以观察到该exe文件包含4个区块表,其中四个区块的信息名称分别为

.text .rdata .data .rsrc

区块解析

首先需要注意的是,区块名称只是为了方便辨识,但对于操作系统来说是无关紧要的,如当寻找输出表、输入表信息时,不应该默认到.text和.rdata区块中寻找,而是要严格依据数据目录数组DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]中的信息进行查找,常见区块如下

名称 描述 .text 默认的代码区块,其中的内容全是指令代码 .data 默认的读、写区块,全局变量、静态变量通常放在此处 .rdata 默认的只读数据区块,程序较少用到该块中的数据,但至少有两种情况会用到,一是在Microsoft链接器产生的exe文件中,用于存放调试目录;二是用于存放说明字符串,如果程序的DEF文件中指定了DESCRIPTION,字符串就会在出现在该块中 .idata 输入表,包含其他外来DLL的函数及数据信息,通常将其合并到其他区块中,如.rdata .edata 输出表,当创建一个输出API或数据的可执行文件时(如DLL),链接器会创建一个.exp文件,.exp文件将会包含一个.edata区块,并加入到最后的可执行文件中,通常将.edata合并到其他块中,如.text区块中 .rsrc 资源,包含模块的全部资源,例如图标、菜单、位图等,该区块是只读的,无论如何都不应该命名为为.rsrc以外的名字,也不能被合并到其他区块中 .reloc 可执行文件的基址重定位,通常只是DLL需要,而exe不需要,通常在Release模式下,链接器不会给exe文件加上基址重定位


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有