Skip to main content

PE 文件

可移植可执行 (PE) 文件格式与 Windows 操作系统上能实现代码执行的文件类型所使用,常见的拓展名有 exedllsys 等。PE 格式描述了文件必须遵循的标准结构,以便定位其内容并在执行的各个阶段使用其信息。了解这种格式对于恶意软件分析人员来说尤为重要,因为检查可执行文件的 PE 内容可以提供有关文件的大量信息,可能包括文件的作用。对于我们开发恶意软件的攻击者也同样重要,因为一些免疫检测的技术需要对 PE 文件十分熟悉。


PE 文件结构

PE 格式包含多种文件类型,在最高层次可以分为 COFF 文件与 PE 文件

image.png


通用目标文件格式 (COFF) 文件,也称为对象文件,具有 obj 文件扩展名。它由 Windows 兼容的编译器生成,将源代码转换为机器代码。该文件类型本身不可执行,但可以作为输入传递给链接器,链接器从一个或多个对象文件创建可执行文件

PE 文件,也称为可执行文件映像文件,这是链接器生成的文件,包含可执行代码运行时映射到内存的数据。映像文件还包含另外两种类型,第一种是动态链接库文件,具有 dll 扩展名,包含可以被多个程序同时导入和使用的代码和数据。 尽管 DLL 文件被归类为可执行文件,但它不能独立地直接运行。第二种是可执行文件,具有 exe 扩展名,与 DLL 不同的是它可以独立运行。

PE 格式以许多文件头开始,文件头是位于数据块起始附加数据,通常包含有关数据块的信息,例如数据块的大小元素在数据块中的位置,以及其他属性。在 PE 格式中,文件头数据由结构体组织和定义,在 C 和 C 相关的编程语言中,结构体是由不同的元素组成的数据类型,这些元素本身可能有多种数据类型。这些元素由不同的变量名称引用,并按顺序存储在连续的内存块中。PE 格式中使用的结构在名为 winnt.h 的头文件中定义,该文件可以作为 Windows SDK 的一部分下载。

image.png

 

DOS 头与 DOS Stub

PE 文件的第一个头文件总是以 0x4D5A (MZ) 这 2 个字节为前缀,这 2 个字节表示 DOS 头签名,用于确认正在解析或检查的文件是有效的 PE 文件。DOS头是一个数据结构体,定义如下:

typedef struct _IMAGE_DOS_HEADER {     
    WORD   e_magic;                     // 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;                       
    WORD   e_cs;                      
    WORD   e_lfarlc;                   
    WORD   e_ovno;                      
    WORD   e_res[4];                   
    WORD   e_oemid;                     
    WORD   e_oeminfo;                   
    WORD   e_res2[10];                  
    LONG   e_lfanew;                    // NT 头的偏移
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

其中,最重要的分别是 e_magic e_lfanew,分别是小端格式的 ASCII 字符 MZ,以及从映像文件起始到 NT 头的偏移。e_lfanew 总是位于 0x3c 处,占 4 个字节

使用 PE-Bear 打开我们上一小节用 C++ 编写的 MessageBox 程序,我们发现最开始确实是 4D 5A,而在 0x3c 处,值为 0xF0。

image.png

而 DOS Stub 位于 0x40 处,DOS Stub 紧接着 DOS 头,因为 DOS 头占用 0x40 字节 (可以根据上文给出的结构体计算出总大小)。DOS Stub 包含了报错信息:该程序不能再 DOS 模式中运行

 

NT 头

NT 头十分重要,其结构体如下,包含了签名File 头Optional 头这 3 个元素,NT 头包含了大量有关 PE 的信息。

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

//64 位
typedef struct _IMAGE_NT_HEADERS64 {
    DWORD                   Signature;
    IMAGE_FILE_HEADER       FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

Signature

在上文,我们知道了 NT 头在 0xF0 偏移处,于是我们定位到 0xF0 处,发现签名元素 0x50450000,是填充了 2 个零字节的字符串 PE,因为该签名占用 4 字节

image.png

 

File 头

File 头的结构体如下:

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

比较重要的元素有 MachineNumberOfSectionsTimeDateStampSizeOfOptionalHeader,以及 Characteristics

image.png

Machine 表示该 PE 文件所期望运行在的 CPU 架构,我们能看到该应用期望在 AMD64 架构 CPU 的系统上运行。

NumberOfSection 表示当前 PE 文件所包含的 PE 区域数量,该程序有 6 个。

TimeDataStamp 表示文件创建的时间,能看到是 2023 年 6 月 19 日

SizeOfOptionalHeader 表示 Optional 头的尺寸,这里是 240

Characteristics 表示该二进制文件的特定属性,例如是 DLL 还是可执行的文件,这里显然是可执行的 exe 文件。

 

Optional 头




PE 区域

txt


data


rdata


idata


reloc


rsrc


bss




DLL 文件特性