初识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个字段。通常用的就是加*字段。
字段6:AddressOfEntryPoint 表 程序入口RVA,即OEP:
``EOP:程序入口点,壳相关概念
``OEP:原本的程序入口点(实际为偏移,+模块基址=实际入口点)
``EP: 被加工后的入口点
字段9:ImageBase 表 模块加载基地址,exe默认0x400000,dll默认0x10000000
``建议装载地址:exe映射加载到内存中的首地址= PE 0处,即实例句柄hInstance
``一般而言,exe文件可遵从装载地址建议,但dll文件无法满足
尾字段:DataDirectory 表 数据目录表,用来定义多种不通用处的数据块。
``存储了PE中各个表的位置,详情参考IMAGE_DIRECTORY_ENTRY...系列宏
这里可以知道我们确定的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信息
在这个数据目录结构体中只有两个成员VirtualAddress
和Size
,这两个成员的含义比较简单,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://www.cnblogs.com/a-s-m/p/12251728.html