1.概述
PE文件分为几个部分,分别是:
- DOS头
- DOS Stub
- NT头(PE头)
- 文件头
- 可选头
- 区段头(一个数组,每个元素都是一个结构体,称之为IMAGE_SECTION_HEADER)
- .text
- .rdata
- .data
- .rsrc
- .reloc
- ...
本篇文章主要围绕整体结构做一个梳理,不会做太多细节的讲解。(比如说可选头里的数据目录表,它包含16种表,会在接下来的系列文章分别进行讲解,熟悉了数据目录表就可以说熟悉了PE)
2.DOS头和DOS Stub
DOS头和DOS Stub在现在的Windows系统上基本都是摆设(对于软件安全还是有一定价值的,PE的变形、压缩、植入都可能利用Stub部分,以后有机会我会写一个系列专门讲解PE文件操作相关的应用技术),它存在的原因是为了向下兼容、适配DOS系统,对于DOS头和它的Stub部分就只要稍微了解一些主要结构体字段就可以了。
PE文件开头的两个字节,在上篇文章介绍过,是字符“MZ”,也就是0x5A4D,它就是DOS头的开端,从这里开始的64个字节大小都是DOS头的范围。
DOS头的结构体是:
struct _IMAGE_DOS_HEADER{
0X00 WORD magic;
0X02 WORD e_cblp;
0X04 WORD e_cp;
0X06 WORD e_crlc;
0X08 WORD e_cparhdr;
0X0A WORD e_minalloc;
0X0C WORD e_maxalloc;
0X0E WORD e_ss;
0X10 WORD e_sp;
0X12 WORD e_csum;
0X14 WORD e_ip;
0X16 WORD e_cs;
0X18 WORD e_lfarlc;
0X1A WORD e_ovno;
0x1C WORD e_res[4];
0x24 WORD e_oemid;
0x26 WORD e_oeminfo;
0x28 WORD e_res2[10];
0x3C DWORD e_lfanew;
};
DOS头的结构体字段虽然很多,但是绝大部分现在都可以不用了解(除非你想做计算机历史学家,^_^)。对于不重要的字段就不解释了,下面出现的结构体也只会摘出重要的字段做出解释。
magic:它几乎永远是5A4D,也就是字符“MZ”。
e_lfanew:指向PE头的文件偏移。下图中e_lfanew的值是0x100,它也几乎总是这个值。PE头就在地址0x100处开始,它通常是0x00004550,也就是PE\0\0。
下图显示了一些信息,其中0x3c处就是e_lfanew!在十六进制工具中明明显示的是00010000,也就是0x10000啊!怎么会是0x100呢?这就需要了解一些小端排序的知识了。
保存在Windows系统中的数据基本上都是小端排序,数据读取的顺序不再是从左往右,而是从高地址往低地址,一个字节一个字节的读。上图中的e_flanew从高地址到低地址依次是:00 00 01 00,因此它其实是0x100。小端排序是一种CPU遵循的标准或者说格式,为了使CPU能够正确读数据,系统中的大部分程序都被编译成了小端排序(Java这样的基于虚拟机编写的程序除外)。
DOS头的大小是64个字节也就是0x40,从0x40开始到e_flanew指向的0x100为止的部分就是DOS Stub了。
DOS Stub是DOS系统的可执行文件,在DOS系统中会执行里面的代码,只需要将一个包含完整DOS头和DOS Stub的Win32程序放到DOS系统中运行就可以执行里面的代码了。
DOS头和DOS Stub就介绍到这里吧。
3.NT头
上面说道e_lfanew指向的文件偏移是0x100,它指向的位置的值是0x00004550,也几乎总是这个值,它转成ASCII码就是PE\0\0。这里就是NT头的开始。下面介绍一下NT头的结构体。
struct _IMAGE_NT_HEADERS{
0x00 DWORD Signature;
0x04 _IMAGE_FILE_HEADER FileHeader;
0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader;
};
Signatue:也就是“PE\0\0”,占4个字节。
FileHeader:文件头,本身也是一个结构体,后面会说
OptionalHeader:可选头,本身也是一个结构体,后面会说
3.1 文件头
文件头占20个字节,文件头也是一个结构体。
struct _IMAGE_FILE_HEADER{
0x00 WORD Machine;
0x02 WORD NumberOfSections;
0x04 DWORD TimeDateStamp;
0x08 DWORD PointerToSymbolTable;
0x0c DWORD NumberOfSymbols;
0x10 WORD SizeOfOptionalHeader;
0x12 WORD Characteristics;
};
Machine:目标平台,对照下面的表
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.(常见)
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2
#define IMAGE_FILE_MACHINE_AM33 0x01d3
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1
#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#define IMAGE_FILE_MACHINE_CEF 0x0CEF
#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian
#define IMAGE_FILE_MACHINE_CEE 0xC0EE
NumberOfSections:区段表数量
SizeOfOptionalHeader:可选头的大小(字节数):32位默认E0H,64位默认F0H(可修改)
Characteristics:文件类型,对照下面的表
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved externel references).(常见)
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM 0x1000 // System File.
#define IMAGE_FILE_DLL 0x2000 // File is a DLL.(常见)
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
3.2 可选头
介绍完NT头的Signature和文件头还有最后一个字段:可选头。虽然它叫可选头,但是它非常的重要,叫“必选头”才对!可选头包含了非常多的信息,它在32位系统中的大小是E0h(224个字节),64位系统中的大小是F0h(240个字节)。
它的结构体:
struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Magic:0x10B是32位PE,0x20B是64位PE,0x107是ROM映像
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b // 32位PE可选头
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b // 64位PE可选头
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC 0x107 // ROM映像
AddressOfEntryPoint://程序入口的RVA值,程序最先执行代码的地址
BaseOfImage:PE文件的装载地址
SizeOfImage:内存中整个PE映像体的尺寸
SectionAlignment:区段在内存中的对齐值,一般为0x1000
FileAlignment:区段在文件中的对齐值,一般为0x200
SizeOfHeaders:所有头+节表的大小,即整个PE头的大小
DllCharacteristics:DLL文件的属性值,DLL文件才会生效,参见下面的表
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 // DLL can move.
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 // Code Integrity Image
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 // Image is NX compatible
#define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200 // Image understands isolation and doesn't want it
#define IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400 // Image does not use SEH. No SE handler may reside in this image
#define IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 // Do not bind this image.
// 0x1000 // Reserved.
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 // Driver uses WDM model
// 0x4000 // Reserved.
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000
NumberOfRvaAndSizes:数据目录成员的数量,一般为0x10
DataDirectory:数据目录表数组,每个结构给出一个重要数据结构的RVA和size,参见下面的表
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
数据目录表的结构体是这样的:
struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
数据目录表的解析后面的文章会逐一介绍,它是PE文件格式的重中之重。
4.区段头和区段
区段头是一个数组,里面的每个元素都是一个结构体,这个结构体叫做IMAGE_SECTION_HEADER,每个结构体的大小是40个字节,如下:
struct _IMAGE_SECTION_HEADER {
BYTE Name[8];
union {
DWORD PhysicalAddress;
WORD VirtualSize;
} Misc
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
Name:区段名
PhysicalAddress:物理地址,它的值一般跟VirtualSize相同,所以它有些不准确。
VirtualSize:真实大小,区段未做对齐处理之前的大小,跟PhysicalAddress同属一个联合体,可以使用任何一个,一般使用VirtualSize。
VirtualAddress:区段在内存中的RVA
SizeOfRawData:区段在磁盘中的大小
PointerToRawData:区段在文件中的偏移
PointerToRelocations:在obj文件中使用,指向重定位表的指针
PointerToLinenumbers:行号表的位置,供调试使用,一般为0x0000
NumberOfRelocations:在obj文件中使用,此区段重定位表项的数量
NumberOfLinenumbers:行号表中的行号数量
Characteristics:区段属性,参见下表
IMAGE_SCN_CNT_CODE 0x00000020 该节区含代码
IMAGE_SCN_MEM_SHARED 0x10000000 该节区为可共享
IMAGE_SCN_MEM_EXECUTE 0x20000000 该节区为可执行
IMAGE_SCN_MEM_READ 0x40000000 该节区为可读
IMAGE_SCN_MEM_WRITE 0x80000000 该节区为可写
区段头介绍完了,已经说明过区段头是一个数组,文件头中有区段的数量,这个数量就是区段头数组的大小,紧接着区段头后面的就是各种区段,常见的区段见下面的表:
.text: 默认的代码区块,内容都是指令代码
.data:默认的读写数据块,全局变量、静态变量一般放在这里。
.rdata: 默认的只读数据块 一般很少用到。
.idata:包含外来的DLL数据及数据信息,也就是输入表 ,通常情况下把他合并到.rdata中。
.edata: 当创建一个用于输出数据的可执行文件时(输出表),数据会放在这里,通常情况下会被合并到.text 或.tdata中。
.rsrs:资源块 包含一切图标菜单等。
5.总结
本文对PE的结构有了一个总体的介绍,到此应该对PE的结构有了一个整体性的了解。PE文件看似简单,实际上使用的时候灵活度很大,对PE文件的变形压缩等操作的前提是要对PE每一个字段、每一处结构有充分的了解,所以PE文件中重要的字段要非常的熟练。
文件结构中有一些字段需要记忆,以加深对PE的印象。
比如说,有大量对属性、类型的定义:
- DOS头中的magic字段:4D5A,"MZ"
- 文件头中的 Machine:目标平台的类型
- 文件头中的Characteristics:文件的类型
- 可选头中的Magic: 文件的类型,重点分别32位还是64位
- 可选头中的 DllCharacteristics:只有DLL文件才有效,定义DLL的一些属性
- 区段头中的Characteristics: 区段的属性
还有各个结构中的重点字段:
- DOS头中的magic、e_lfanew
- 文件头中的machine、characteristics、numberOfSections、sizeOfOptionalHeader
- 可选头中的magic、DllCharacteristics、addressOfEntry、imageOfBase、sizeOfHeaders、sizeOfImage
- 区段头中的VirtualSize、VirtualAddress、PointerToRawData、sizeOfRawData、numberOfRelocations、pointerToRelocations、numberOfLinenumbers、pointerToLinenumbers、name、Characteristics
下面的图标明了PE文件各部分的范围:
上图中,区段头有五个成员,每一个长度都是28h,意味着这个文件有5个区段。
区段头的后面5个区段依次排列。每个区段的起始点在区段头的pointerToRawdata中记录,长度在sizeOfRawData中记录。
再后面的内容存储了很多表的内容以及杂项。这些表都在可选头的数据目录表里记录,起始地址就保存在数据目录表里,下篇文章开始介绍数据目录表里的内容。