Fork me on GitHub

写壳专题之PE文件格式的了解(一)

PE 的意思是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行文件格式。它的一些特性继承自Unix的COFF(Common Object File Format)文件格式。“Portable Executable”(可移植的执行体)意味着此文件格式是跨Win32平台的;即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。 

PE文件在文件系统中,与存贮在磁盘上的其它文件一样,都是二进制数据,对于操作系统来讲,可以认为是特定信息的一个载体,如果要让计算机系统执行某程序,则程序文件的载体必须符合某种特定的格式。要分析特定信息载体的格式,要求分析人员有数据分析、编码分析的能力。在Win32系统中,PE文件可以认为.exe、.dll、.sys 、.scr类型的文件,这些文件在磁盘上存贮的格式都是有一定规律的。

PE格式基础                                                                                          

 下表列出了PE的总体结构:

DOS MZ header
DOS stub
PE header
Section table
Section 1
Section 2
…
Section n

一个完整的PE文件,前五项是必定要有的,如果缺少或者数据出错,系统会拒绝执行该文件。

  a.文件头格式错误:......不是有效的Win32 应用程序。

  b.格式数据错误:应用程序正常初始化(0x********)失败。请单击“确定”,终止应用程序。

  c.代码错误:直接要求发送错误报告。

DOS MZ header部分是DOS时代遗留的产物,是PE文件的一个遗传基因,一个Win32程序如果在DOS下也是可以执行,只是提示:“This program cannot be run in DOS mode.”然后就结束执行,提示执行者,这个程序要在Win32系统下执行。

DOS stub 部分是DOS插桩代码,是DOS下的16位程序代码,只是为了显示上面的提示数据。这段代码是编译器在程序编译过程中自动添加的。

PE header 是真正的Win32程序的格式头部,其中包括了PE格式的各种信息,指导系统如何装载和执行此程序代码。

Section table部分是PE代码和数据的结构数据,指示装载系统代码段在哪里,数据段在哪里等。对于不同的PE文件,设计者可能要求该文件包括不同的数据的Section。所以有一个Section Table 作为索引。Section多少可以根据实际情况而不同。但至少要有一个Section。如果一个程序连代码都没有,那么他也不能称为可执行代码。在Section Table后,Section数目的多少是不定的。

 程序的装入                                                                                          

当我们在explorer.exe(资源管理器)中双击某文件,执行一个可执行程序,系统会根据文件扩展名启动一个程序装载器,称之为Loader。Loader会首先检查DOS MZ Header,如果存在,就继续寻找PE header,如果这两项都不存在,就认为是DOS 16位代码,如果只存在DOS MZ Header,而其中又没指示了PE Header 的位置,那么Loader 就判定此文件不一个有效的PE文件,拒绝执行。

如果DOS Header 和PE Header都正常有效,那么Loader就会根据PE Header 及Section Table的指示,将相应的代码和数据映射到内存中,然后根据不同的Section进行数据的初始化,最后开始执行程序段代码。

 PE格式高级分析                                                                                    

下面我们以一个真实的程序为例详细分析PE格式,分析PE格式最好有PE分析器,常用的软件是Lord PE,也有其它的分析工具和软件如PE Editor 、Stud PE等。

先分析一下磁盘文件的内容,这里我们使用UltraEdit32(UE)工具,这是一个实用的文件编辑器,可以编辑文本和二进制文件。

在文件的一开始有两位16进制数据4D 5A,其对应的ASCII字符是MZ,这个标志就是DOS MZ Header 的标志。下面是通过Load PE列出的DOS MZ Header

1.DOS Header

->DOS Header
   e_magic:     0x5A4D
   e_cblp:      0x0090
   e_cp:        0x0030
   e_crlc:      0x0000
   e_cparhdr:   0x0004
   e_minalloc:  0x0000
   e_maxalloc:  0xFFFF
   e_ss:        0x0000
   e_sp:        0x00B8
   e_csum:      0x0000
   e_ip:        0x0000
   e_cs:        0x0000
   e_lfarlc:    0x0040
   e_ovno:      0x0000
   e_res:       0x0000000000000000
   e_oemid:     0x0000
   e_oeminfo:   0x0000
   e_res2:      0x00000000000000000000000000000000
   e_lfanew:    0x000000E0

这是一个PE文件的DOS Header,其中我们最关心的就是e_lfanew这个字段的值,它指向了PE Header 在磁盘文件中相对于文件开始的偏移地址,这里是E0。在本文件00E0h处果然找到了“PE”两个字符,那么在00E0h处就是PE Header 的有效头载荷。

2.PE Header

2.1 IMAGE_NT_HEADERS:

//Winnt.h
typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;  // PE文件头标志:PE/0/0。在偏移3CH(e_lfanew)处所指向的地址开始
    IMAGE_FILE_HEADER FileHeader;    // PE文件物理分布的信息
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;    // PE文件逻辑分布的信息
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32

2.2 IMAGE_FILE_HEADER和IMAGE_OPTIONAL_HEADER

//Winnt.h
typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;    // 运行所需要的CPU,对于Intel平台是14Ch
    WORD    NumberOfSections;    // 文件的节数目
    DWORD   TimeDateStamp;    // 文件创建日期和时间
    DWORD   PointerToSymbolTable;    // 用于调试
    DWORD   NumberOfSymbols;    // 符号表中符号数
    WORD    SizeOfOptionalHeader;    // OptionalHeader结构大小
    WORD    Characteristics;    // 文件信息标记,区分文件时exe还是dll
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 

// Important Characteristics value
#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // 文件中是否存在重定位信息
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // 文件是可执行的
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // 程序可以触及大于2G的地址
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // 保留的机器类型低位
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32位机器
#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  // 保留的机器类型高位

 

//Winnt.h
typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD    Magic;    // 标志字(总是010bh)
    BYTE    MajorLinkerVersion;    // 连接器高版本号
    BYTE    MinorLinkerVersion;    // 链接器低版本号
    DWORD   SizeOfCode;    // 代码段大小
    DWORD   SizeOfInitializedData;    // 已初始化数据块大小
    DWORD   SizeOfUninitializedData;    // 未初始化数据块大小
    DWORD   AddressOfEntryPoint;    // PE装载器准备运行的PE文件的第一个指令的RVA,若要改变整个执行的流程,可以将该值指定到新的RVA,这样新的RVA处的指令首先被执行。 
    DWORD   BaseOfCode;    // 代码段起始RVA
    DWORD   BaseOfData;    // 数据段起始RVA
    DWORD   ImageBase;    // PE文件的装载地址
    DWORD   SectionAlignment;    // 块对齐因子
    DWORD   FileAlignment;    // 文件块对齐因子
    WORD    MajorOperatingSystemVersion;    // 所需操作系统高位版本号
    WORD    MinorOperatingSystemVersion;    // 所需操作系统低位版本号
    WORD    MajorImageVersion;    // 用户自定义高位版本号
    WORD    MinorImageVersion;    // 用于自定义低位版本号
    WORD    MajorSubsystemVersion;    // Win32子系统版本。若PE文件是专门为Win32设计的
    WORD    MinorSubsystemVersion;    // 该子系统版本必定是4.0,否则对话框不会有3维立体感
    DWORD   Win32VersionValue;    // 保留值,系统没用,一般作为是否被感染的标志
    DWORD   SizeOfImage;    // 内存中整个PE镜像体的尺寸
    DWORD   SizeOfHeaders;    // 所有头+节表的大小
    DWORD   CheckSum;    // 校验和
    WORD    Subsystem;    // NT用来识别PE文件属于哪个子系统
    WORD    DllCharacteristics;    // 用来表示一个dll镜像是否为进程和线程的初始化及终止包含入口点的标记
    DWORD   SizeOfStackReserve;    
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;    // 堆栈大小 这些域控制要保留的地址空间数量,并且负责栈和默认堆的申请。在默认情况下,栈和堆都拥有一个页面的申请值以及16个页面的保留值
    DWORD   LoaderFlags;    // 告知装载器是否在装载时终止和调试,或者在默认地正常运行
    DWORD   NumberOfRvaAndSizes;    // 该字段标识了接下来的DataDirectory数组个数
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];    //IMAGE_DATA_DIRECRORY结构数组。每个结构给出一个重要数据结构的RVA,比如引入地址表等。
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

 2.3 IMAGE_DATA_DIRECTORY

//Winnt.h
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;    // 表的RVA地址
    DWORD   Size;    // 大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

 Load PE 读取的PE Header 的重要部分数据:

Subsystem 类型:

 Load PE 读取的IMAGE_DATA_DIRECTORY 信息:

在IMAGE_OPTIONAL_HEADER32后部一般是16项IMAGE_DATA_DIRECTORY数据,其中最后一项是保留数据。每一项数据都有其固定的含义,并且位置不可改变。

Export Table 导出函数表,主要用于DLL中的导出函数
Import Table 导入函数表,使用外部函数的数据表
Resource  资源数据表
Exception  异常处理表
Security  安全处理数据表
Relocation  重定位信息表,一般和DLL相关
Debug   调试信息表
Copyright  版权信息表
Globalptr  机器值(MIPS GP)
Tls Table  线程信息表
LoadConfig  装配信息表
BoundImport  输入函数绑定信息表
IAT    也ImportTable对应,由Loader填写的输入函数地址
DelayImport  延迟装入的函数信息
COM    公用组件信息表
Reserved  保留信息,系统没有使用,为以后扩展使用
这16项数据,其所在位置由RVA指定,大小由Size指定。对于一般的一个可执行程序(.exe),最重要的是导入表(Import Table和IAT)、资源数据表(Resoruce)。

未完待续。。。

2013/12/27 17:14

转载请注明出处:http://www.cnblogs.com/jackbase/articles/3493674.html                       


 

 

posted @ 2013-12-27 10:28  Jackbase  阅读(798)  评论(1编辑  收藏  举报