没有合适的资源?快使用搜索试试~ 我知道了~
首页PE文件详解-附图,具体
PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任何扩展名。那Windows是怎么区分可执行文件和非可执行文件的呢?我们调用LoadLibrary传递了一个文件名,系统是如何判断这个文件是一个合法的动态库呢?这就涉及到PE文件结构了 --------------------- 作者:evileagle
资源详情
资源评论
资源推荐

PE (Portable Execute )文件是 Windows 下可执行文件的总称,常见的有 DLL ,EXE ,OCX ,SYS 等,事
实上,一个文件是否是 PE 文件与其扩展名无关, PE 文件可以是任何扩展名。那 Windows 是怎么区分可执行
文件和非可执行文件的呢?我们调用 LoadLibrary 传递了一个文件名,系统是如何判断这个文件是一个合法
的动态库呢?这就涉及到 PE文件结构了。
PE 文件的结构一般来说如下图所示:从起始位置开始依次是 DOS 头, NT头,节表以及具体的节。
DOS 头是用来兼容 MS-DOS 操作系统的,目的是当这个文件在 MS-DOS 上运行时提示一段文字,大部分情
况下是: This program cannot be run in DOS mode. 还有一个目的,就是指明 NT头在文件中的位置。
NT头包含 windows PE 文件的主要信息,其中包括一个 ‘PE’字样的签名, PE 文件头
(IMAGE_FILE_HEADER )和 PE 可选头( IMAGE_OPTIONAL_HEADER32 ),头部的详细结构以及其具
体意义在 PE文件头文章中详细描述。
节表:是 PE文件后续节的描述, windows 根据节表的描述加载每个节。
节:每个节实际上是一个容器,可以包含代码、数据等等,每个节可以有独立的内存权限,比如代码节默
认有读 /执行权限,节的名字和数量可以自己定义,未必是上图中的三个。
当一个 PE 文件被加载到内存中以后,我们称之为 “映象 ”(image ),一般来说, PE文件在硬盘上和在内存
里是不完全一样的,被加载到内存以后其占用的虚拟地址空间要比在硬盘上占用的空间大一些,这是因为
各个节在硬盘上是连续的,而在内存中是按页对齐的,所以加载到内存以后节之间会出现一些 “空洞 ”。
因为存在这种对齐,所以在 PE结构内部,表示某个位置的地址采用了两种方式,针对在硬盘上存储文件中
的地址,称为原始存储地址或物理地址表示距离文件头的偏移;另外一种是针对加载到内存以后映象中的
地址,称为相对虚拟地址( RVA),表示相对内存映象头的偏移。
第1页 共13页

然而 CPU 的某些指令是需要使用绝对地址的,比如取全局变量的地址,传递函数的地址编译以后的汇编指
令中肯定需要用到绝对地址而不是相对映象头的偏移,因此 PE文件会建议操作系统将其加载到某个内存地
址(这个叫基地址),编译器便根据这个地址求出代码中一些全局变量和函数的地址,并将这些地址用到
对应的指令中。例如在 IDA 里看上去是这个样子:
这里写图片描述
这种表示方式叫做虚拟地址( VA)。也许有人要问,既然有 VA这么简单的表示方式为什么还要有前面的
RVA呢?因为虽然 PE 文件为自己指定加载的基地址,但是 windows 有茫茫多的 DLL ,而且每个软件也有自己
的DLL ,如果指定的地址已经被别的 DLL 占了怎么办?如果 PE文件无法加载到预期的地址,那么系统会帮
他重新选择一个合适的基地址将他加载到此处,这时原有的 VA就全部失效了, NT头保存了 PE 文件加载所需
的信息,在不知道 PE 会加载到哪个基地址之前, VA是无效的,所以在 PE 文件头中大部分是使用 RVA来表示
地址的,而在代码中是用 VA表示全局变量和函数地址的。那又有人要问了,既然加载基址变了以后 VA都失
效了,那存在于代码中的那些 VA怎么办呢?答案是:重定位。系统有自己的办法修正这些值,到后续重定
位表的文章中会详细描述。既然有重定位,为什么 NT头不能依靠重定位采用 VA表示地址呢(十万个为什
么)?因为不是所有的 PE 都有重定位,早期的 EXE 就是没有重定位的。我们都知道 PE文件可以导出函数让
其他的 PE 文件使用,也可以从其他 PE 文件导入函数,这些是如何做到的? PE文件通过导出表指明自己导
出那些函数,通过导入表指明需要从哪些模块导入哪些函数。导入和导出表的具体结构会在单独的文章中
详细解释。
一、 DOS 头
DOS 头的作用是兼容 MS-DOS 操作系统中的可执行文件,对于 32位 PE文件来说, DOS 所起的作用就是显示
一行文字,提示用户:我需要在 32位windows 上才可以运行。我认为这是个善意的玩笑,因为他并不像显示
的那样不能运行,其实已经运行了,只是在 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
第2页 共13页

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;
我们只需要关注两个域:
e_magic :一个 WORD 类型,值是一个常数 0x4D5A ,用文本编辑器查看该值为 ‘MZ’,可执行文件必须都是
'MZ' 开头。
e_lfanew :为 32 位可执行文件扩展的域,用来表示 DOS 头之后的 NT头相对文件起始地址的偏移。
二、 NT 头
顺着 DOS 头中的 e_lfanew ,我们很容易可以找到 NT头,这个才是 32 位PE 文件中最有用的头,定义如下 :
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
下图是一张真实的 PE 文件头结构以及其各个域的取值:
第3页 共13页
剩余12页未读,继续阅读













安全验证
文档复制为VIP权益,开通VIP直接复制

评论0