初识PE文件--dos头、pe头


初识PE文件

0x00 前言

PE(Portable Executable),即可移植的执行体。

Linux平台:ELF(Executable and Linking Format)文件结构。

一般在Windows平台下,所有的可执行文件诸如:exe、dll、sys、ocx、com等均适用PE文件结构。这些使用PE文件结构也被称为PE文件。

0x01 PE 结构

PE 结构是由若干个复杂的结构体组合而成的,不是单单的一个结构体那么简单,它的结构就像文件系统的结构是由多个结构体组成的。

PE 结构包含的结构体有 DOS 头、PE 标识、文件头、可选头、目录结构、节表等。

Windows下如何判断文件是否是PE文件?

1.通过导入文件到c32asm等工具,观察MZ头。

2.通过lordpe等工具。

从 数据管理的角度来看,可以把 PE 文件大致分为两部分,DOS 头、PE 头和节表属于 PE 文件的数据管理结构或数据组织结构部分,而节表数据才是 PE 文件真正的数据部分,其中包含着代码、数据、资源等内容。

从PE结构图中可以看出,PE 结构主要分为 4 大部分(DOS头、PE头、节表、节表数据),其中每个部分又进行了细分,存在若干个小的部分。

DOS头:

无论是32位或64位可执行文件,其文件的头部必定是IMAGE_DOS_HEADER.

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

而DOS头又分两部分:

MZ文件头和Dos Stub

MZ文件头:IMAGE_DOS_HEADER 结构体,其大小占64个字节,并且该结构中的最后一个LONG类型e_lfanew成员指向PE文件头的位置为中的PE文件头标志的地址。

这里有两个比较有用的成员信息:

1、e_magic,用于判断PE文件的标识。如果不是MZ即不是十六进制值:0x5A4D。计算机存储顺序是低位在前高位在后,所以存储为:0x4D5A。

2、e_lfanew,这里是指pe的偏移量,用于找到pe头的位置。

如下阴影区域:

DOS stub:dos存根,在IMAGE_DOS_HEADER和IMAGE_NT_HEADERS之间存在一DOS存根,这其实是一段汇编代码:

PE文件是运行在32位或64位操作系统下的。

其功能是当该EXE运行在16位环境下,输出一段文字:“This program cannot be run in DOS mode”,然后并退出该进程。

在pe文件利用的时候,我们可以把payload写入到当前区域,诸如存放我们的shellcode,在读取时,获取dos头字节数,减去MZ头字节数,即为dos存根字节大小。然后拿去操作加载shellcode等。

PE头:

在MS-DOS头下main,就是PE头,PE头是PE相关结构NT映像头(IMAGE_NT_HEADER)的简称,其中包含许多PE装载器用到的重要字段。

IMAGE_NT_HEADER数据结构定义:

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

参数具体含义:

Signature

将文件标识为 PE 映像的 4 字节签名。字节为“PE\0\0”。这个字段是PE文件的标志字段,通常设置成00004550h,其ASCII码为PE00,这个字段是PE文件头的开始,前面的DOS_HEADER结构中的字段e_lfanew字段就是指向这里。

FileHeader

指定文件头的 IMAGE_FILE_HEADER结构。

IMAGE_FILE_HEADER结构

这个字段也是包含几个字段结构,它包含了PE文件的一些基本信息,最重要的是其中一个域指出了IMAGE_OPTIONAL_HEADER的大小。

typedef struct _IMAGE_FILE_HEADER {

WORD Machine;//运行平台

WORD NumberOfSections;//文件的区块数目

DWORD TimeDateStamp;//文件创建的用时间戳标识的日期

DWORD PointerToSymbolTable;//指向符号表(用于调试)

DWORD NumberOfSymbols;//符号表中符号的个数

WORD SizeOfOptionalHeader;//IMAGE_OPTIONAL_HEADER32结构大小

WORD Characteristics;//文件属性

} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

在PE文件头的后面,1234567个框分别对应_IMAGE_FILE_HEADER结构的七个参数位置以及各自的值。我们需要判断运行平台,就可以通过第一个参数位置的值来判断。

上述e_lfanew中,可以在下图中看到,e_lfanew的值为0080,这里可以看到PE头就在0080h。

常见标识如下,比如这里的014c,就是在Intel I386机器上运行。

机器 标识

Intel I386 14ch

MIPS R3000 162h

Alpha AXP 184h

Power PC 1F0h

MIPS R4000 184h

2)NumberOfSection,标识区块的数目,关于区块后面会详细讲。

3)TimeDateStamp

这个字段没啥好说的,指的就是PE文件创建的事件,这个时间是指从1970年1月1日到创建该文件的所有的秒数。

4)PointerToSymbolTable。这个字段用的比较少,略

5)NumberOfSymbol。这个字段也用得很少,略

6)SizeOfOptionalHeader:紧跟着IMAGE_FILE_HEADER后面的数据大小,这也是一个数据结构,它叫做IMAGE_OPTIONAL_HEADER,其大小依赖于是64位还是32位文件。32位文件值通常是00EOh,对于64位值通常为00F0h。

7)Characteristics:文件属性,普通EXE文件这个字段值为010fh,DLL文件这个字段一般是0210h。

IMAGE_OPTIONAL_HEADER结构

OptionalHeader

指定可选文件头的 IMAGE_OPTIONAL_HEADER结构。

这个结构是IMAGE_FILE_HEADER结构的补充。这两个结构合起来才能对整个PE文件头进行描述。左边的16位字符表示相对于文件头的偏移量。

typedef struct _IMAGE_OPTIONAL_HEADER 
{
  //
  // Standard fields. 
  //
+18h  WORD  Magic;     // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
+1Ah  BYTE   MajorLinkerVersion;   // 链接程序的主版本号
+1Bh  BYTE   MinorLinkerVersion;   // 链接程序的次版本号
+1Ch  DWORD  SizeOfCode;   // 所有含代码的节的总大小
+20h  DWORD  SizeOfInitializedData;  // 所有含已初始化数据的节的总大小
+24h  DWORD  SizeOfUninitializedData; // 所有含未初始化数据的节的大小
+28h  DWORD  AddressOfEntryPoint;  // 程序执行入口RVA
+2Ch  DWORD  BaseOfCode;   // 代码的区块的起始RVA
+30h  DWORD  BaseOfData;   // 数据的区块的起始RVA
  //
  // NT additional fields.  以下是属于NT结构增加的领域。
  //
+34h  DWORD  ImageBase;   // *********程序的首选装载地址
+38h  DWORD  SectionAlignment;   // *********内存中的区块的对齐大小
+3Ch  DWORD  FileAlignment;   // *********文件中的区块的对齐大小
+40h  WORD  MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号
+42h  WORD  MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号
+44h  WORD  MajorImageVersion;    // 可运行于操作系统的主版本号
+46h  WORD  MinorImageVersion;    // 可运行于操作系统的次版本号
+48h  WORD  MajorSubsystemVersion; // 要求最低子系统版本的主版本号
+4Ah  WORD  MinorSubsystemVersion; // 要求最低子系统版本的次版本号
+4Ch  DWORD  Win32VersionValue;    // 莫须有字段,不被病毒利用的话一般为0
+50h  DWORD  SizeOfImage;    // 映像装入内存后的总尺寸
+54h  DWORD  SizeOfHeaders;    // 所有头 + 区块表的尺寸大小
+58h  DWORD  CheckSum;    // 映像的校检和
+5Ch  WORD  Subsystem;    // 可执行文件期望的子系统
+5Eh  WORD  DllCharacteristics;    // DllMain()函数何时被调用,默认为 0
+60h  DWORD  SizeOfStackReserve;    // 初始化时的栈大小
+64h  DWORD  SizeOfStackCommit;    // 初始化时实际提交的栈大小
+68h  DWORD  SizeOfHeapReserve;    // 初始化时保留的堆大小
+6Ch  DWORD  SizeOfHeapCommit;    // 初始化时实际提交的堆大小
+70h  DWORD  LoaderFlags;    // 与调试有关,默认为 0 
+74h  DWORD  NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT 发布以来    // 一直是16
+78h  DWORD  DataDirctory[16];     // ********* 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

这里总共31个字段。通常用的就是加*字段。这里可以知道我们确定的PE文件头在0080h处,通过偏移计算,我们可以得到IMAGE_OPTIONAL_HEADER结构的的首个字段在80h+18h=98h的地方。这里直接通过c32asm跳转到对应位置。如图所示:

然后比较重要的就是最后一个成员,即数据目录表,大小为16,每个元素都是一个IMAGE_DATA_DIRECTORY结构体,这里看到是一个数组类型。

它的定义如下:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD VirtualAddress; //所指向的数据结构的虚拟地址
    DWORD Size; //数据结构的大小
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;

在winnt.h中的定义:

#define IMAGE_DIRECTORY_ENTRY_EXPORT		   //0 导出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT		   //1 导入表 
#define IMAGE_DIRECTORY_ENTRY_RESOURCE		 //2 资源目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION		 //3 异常目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY		 //4 安全目录
#define IMAGE_DIRECTORY_ENTRY_BASERELOC	   //5 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_DEBUG		     //6 调试目录
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT		 //7 描术字串
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR		 //8 机器值
#define IMAGE_DIRECTORY_ENTRY_TLS		       //9 TLS目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG	 //10 载入配值目录
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT //11 绑定输入表
#define IMAGE_DIRECTORY_ENTRY_IAT		       //12 导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT //13 延迟载入描述
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR     //14 COM信息

在这个数据目录结构体中只有两个成员VirtualAddressSize,这两个成员的含义比较简单,VirtualAddress指定了数据块的相对虚拟地址(RVA)。Size则指定了该数据块的大小,有时并不是该类型数据的总大小,可能只是该类型数据一个数据项的大小。这两个成员(主要是VirtualAddress)成为了定位各种表的关键,所以一定要知道每个数组元素所指向的数据块类型,以下表格就是它的对应关系:

下面是DataDirctory[16]即数据目录表的各个成员

索 引 索引值在Windows.inc中的预定义值 对应的数据块 偏移量
0 IMAGE_DIRECTORY_ENTRY_EXPORT 导出表 78h
1 IMAGE_DIRECTORY_ENTRY_IMPORT 导入表 80h
2 IMAGE_DIRECTORY_ENTRY_RESOURCE 资源 88h
3 IMAGE_DIRECTORY_ENTRY_EXCEPTION 异常(具体资料不详) 90h
4 IMAGE_DIRECTORY_ENTRY_SECURITY 安全(具体资料不详) 98h
5 IMAGE_DIRECTORY_ENTRY_BASERELOC 重定位表 A0h
6 IMAGE_DIRECTORY_ENTRY_DEBUG 调试信息 A8h
7 IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 版权信息 B0h
8 IMAGE_DIRECTORY_ENTRY_GLOBALPTR 具体资料不详 B8h
9 IMAGE_DIRECTORY_ENTRY_TLS Thread Local Storage C0h
10 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 具体资料不详 C8h
11 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 具体资料不详 D0h
12 IMAGE_DIRECTORY_ENTRY_IAT 导入函数地址表 D8h
13 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 具体资料不详 E0h
14 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 具体资料不详 E8h
15 未使用 保留

对于安全人员来说,通常需要了解比较重要的导出表和导入表。这里放在下篇学习。

0x03 参考

https://blog.csdn.net/obuyiseng/article/details/50231631

PE