“ PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格
式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。”portable executable”
(可移植的执行体)意味着此文件格式是跨win32平台的 :
即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。当然,移植到不
同的CPU上PE执行体必然得有一些改变。所有 win32执行体
(除了VxD和16位的Dll)都使用PE文件格式,包括NT的内核模式驱动程序(kernel mode
drivers)。因而研究PE文件格式给了我们洞悉Windows结构的良机。“

      好了,上面这段话就是我们为什么要研究PE文件结构。看图

这张图我相信大家不陌生,第一块是DOS MZ header这是什么呢?

     这张图的每一块都是什么意思呢?

那么我们就来开始动手。

我们要设计我们自己的PE TOOLS—-PE Show

主要功能:1判断文件是否是PE文件。

          2显示pe文件的相关信息。

1.打开文件代码如下:

代码:

——————————————————————————–

if(FALSE==PEfile.Open(m_filename,CFile::typeBinary|CFile::shareDenyNone))
 {
   MessageBox("文件打不开!");
   return;
 }

——————————————————————————–

CFile类的使用方法希望大及自己去查找msdn

2。文件我们打开了而且是以Binary方式打开的,下面我们该干什么了?

 编写第一个功能—–检验PE文件的有效性

在Iczelion’s的教程中有这样一段话:

“1。首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE,是则 DOS MZ header 有效。

2。一旦证明文件的 DOS header 有效后,就可用e_lfanew来定位 PE header 了。

3。比较 PE header 的第一个字的值是否等于 IMAGE_NT_HEADER。

   如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。

这就是检验PE文件有效性的流程。

从上面那段话我们看出判断的关键是PE header 的第一个字的值是否等于 IMAGE_NT_HEADER

直到这些我们就倒着找。

PE header 的第一个字的值是什么?

我么就来看一下IMAGE_NT_HEADERS的结构:(查看WINNT.H就找到了)

代码:

——————————————————————————–

typedef struct _IMAGE_NT_HEADERS {
   DWORD Signature;
   IMAGE_FILE_HEADER FileHeader;
   IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

——————————————————————————–

Signature 一dword类型,值为50h, 45h, 00h, 00h(PE\0\0)。 本域为PE标记,我们可以此识别给定文件是否为有效PE文件。

FileHeader 该结构域包含了关于PE文件物理分布的信息, 比如节数目、文件执行机器等。

OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息,虽然域名有”可选”字样,但实际上本结构总是存在的。

我们目的很明确。如果IMAGE_NT_HEADERS的signature域值等于”PE\0\0″,那么就是有效的PE文件。实际上,为了比较方便,Microsoft已定义了常量

代码:

——————————————————————————–

IMAGE_NT_SIGNATURE供我们使用。

IMAGE_DOS_SIGNATURE equ 5A4Dh

IMAGE_OS2_SIGNATURE equ 454Eh

IMAGE_OS2_SIGNATURE_LE equ 454Ch

IMAGE_VXD_SIGNATURE equ 454Ch

IMAGE_NT_SIGNATURE equ 4550h

——————————————————————————–

判断的问题我们解决了,新的问题又来了我们如何定位IMAGE_NT_HEADERS结构的位置。

MS肯定有办法,PE文件的开头是什么?DOS MZ header结构,我们来看一下他的定义:

代码:

——————————————————————————–

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
   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;

——————————————————————————–

 看一下最后一项!!!!!

 发现了吗?他指向的就是PE header

 那么DOS MZ header的位置怎么确定呢?呵呵,他就是文件的开始。

综上所述:实际上过程很简单,一旦程序执行,首先确认DOS MZ header的位置(文件开始处),然后判断_IMAGE_DOS_HEADER中的第一个字e_magic是否等于MZ;如果不是,此时在dos操作系统下执行,此时由DOS stub负责,一般是This program cannot run in DOS mode”。如果是,则根据e_lfanew指针跳到PE Header处,此时判断PE Header中,结构 _IMAGE_NT_HEADERS中Signature的值是否是IMAGE_NT_SIGNATURE。如果是说明是正确的pe文件。

下面将总结一下装载一PE文件的主要步骤:

当PE文件被执行,PE装载器检查 DOS MZ header 里的 PE header 偏移量。如果找到,则跳转到 PE header。

PE装载器检查 PE header 的有效性。如果有效,就跳转到PE header的尾部。

紧跟 PE header 的是节表。PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。

PE文件映射入内存后,PE装载器将处理PE文件中类似 import table(引入表)逻辑部分。

PE header 的正式命名是 IMAGE_NT_HEADERS。再来回忆一下这个结构。

IMAGE_NT_HEADERS STRUCT

   Signature dd ?

   FileHeader IMAGE_FILE_HEADER <>

   OptionalHeader IMAGE_OPTIONAL_HEADER32 <>

IMAGE_NT_HEADERS ENDS

Signature PE标记,值为50h, 45h, 00h, 00h(PE\0\0)。

FileHeader 该结构域包含了关于PE文件物理分布的一般信息。

OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息。

最有趣的东东在 OptionalHeader 里。不过,FileHeader 里的一些域也很重要。我们将学习FileHeader,下一课研究OptionalHeader。

IMAGE_FILE_HEADER STRUCT
   Machine WORD ?
   NumberOfSections WORD ?
   TimeDateStamp dd ?
   PointerToSymbolTable dd ?
   NumberOfSymbols dd ?
   SizeOfOptionalHeader WORD ?
   Characteristics WORD ?
IMAGE_FILE_HEADER ENDS
Field name
Meanings
Machine

Field
name

Meanings

Machine

该文件运行所要求的CPU。对于Intel平台,该值是IMAGE_FILE_MACHINE_I386 (14Ch)。我们尝试了LUEVELSMEYERpe.txt声明的14Dh14Eh,但Windows不能正确执行。看起来,除了禁止程序执行之外,本域对我们来说用处不大。

NumberOfSections

文件的节数目。如果我们要在文件中增加或删除一个节,就需要修改这个值。

TimeDateStamp

文件创建日期和时间。我们不感兴趣。

PointerToSymbolTable

用于调试。

NumberOfSymbols

用于调试。

SizeOfOptionalHeader

指示紧随本结构之后的 OptionalHeader 结构大小,必须为有效值。

Characteristics

关于文件信息的标记,比如文件是exe还是dll

简言之,只有三个域对我们有一些用: Machine, NumberOfSections 和 Characteristics。通常不会改变 Machine 和Characteristics 的值,但如果要遍历节表就得使用NumberOfSections。

为了更好阐述 NumberOfSections 的用处,这里简要介绍一下节表。

节表是一个结构数组,每个结构包含一个节的信息。因此若有3个节,数组就有3个成员。 我们需要NumberOfSections值来了解该数组中到底有几个成员。 也许您会想检测结构中的全0成员起到同样效果。Windows确实采用了这种方法。为了证明这一点,可以增加NumberOfSections的值,Windows仍然可以正常执行文件。据我们的观察,Windows读取NumberOfSections的值然后检查节表里的每个结构,如果找到一个全0结构就结束搜索,否则一直处理完NumberOfSections指定数目的结构。 为什么我们不能忽略NumberOfSections的值? 有几个原因。PE说明中没有指定节表必须以全0结构结束。Thus there may be a situation where the last array member is contiguous to the first section, without empty space at all. Another reason has to do with bound imports. The new-style binding puts the information immediately following the section table’s last structure array member. 因此您仍然需要NumberOfSections。

也就是说:PE结构中的NumberOfSections值指定有多少个节。

打赏