“ PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格
式。它的一些特性继承自 Unix Coff (common object file format)文件格式。"portable executable"
(可移植的执行体)意味着此文件格式是跨win32平台的 :
即使Windows运行在非IntelCPU上,任何win32平台的PE装载器都能识别和使用该文件格式。当然,移植到不
同的CPUPE执行体必然得有一些改变。所有 win32执行体
(
除了VxD16位的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, 00hPE\0\0)。 本域为PE标记,我们可以此识别给定文件是否为有效PE文件。
FileHeader 该结构域包含了关于PE文件物理分布的信息, 比如节数目、文件执行机器等。
OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。
我们目的很明确。如果IMAGE_NT_HEADERSsignature域值等于"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_HEADERSSignature的值是否是IMAGE_NT_SIGNATURE。如果是说明是正确的pe文件。

 

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

  1. PE文件被执行,PE装载器检查 DOS MZ header 里的 PE header 偏移量。如果找到,则跳转到 PE header
  2. PE装载器检查 PE header 的有效性。如果有效,就跳转到PE header的尾部。
  3. 紧跟 PE header 的是节表。PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。
  4. 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, 00hPE\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

该文件运行所要求的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值指定有多少个节。

 

 

 

 

 

 

 

posted on 2009-04-18 00:59  jasonM  阅读(4437)  评论(2编辑  收藏  举报