前言
这篇是关于PE结构学习的笔记,参考了两篇文章做了个综合
PE文件解析
0x01可执行文件
文件格式一般是指数据信息在计算机中存储的格式,不同计算机操作系统,不同应用的文件,在计算机中存储都有可能是不同的
可执行文件:一般是指可以被计算机操作系统识别并执行的文件
windows: 采用PE(Portable Executable)格式
linux:采用ELF(Executable and Linking Format)格式
0x02 PE文件格式
PE文件,常见的如:exe,dll,ocx,sys,com等文件后缀的文件都属于PE格式
0x03 地址的基本概念
VA(Virtual Address):虚拟地址
PE 文件映射到内存空间时,数据在内存空间中对应的地址。
ImageBase:映射基址
PE 文件在内存空间中的映射起始位置,是个 VA 地址。
RVA(Relative Virtual Address):相对虚拟地址
PE 文件在内存中的 VA 相对于 ImageBase 的偏移量。
FOA(File Offset Address,FOA):文件偏移地址
PE 文件在磁盘上存放时,数据相对于文件开头位置的偏移量,文件偏移地址等于文件地址。
节偏移 : 节偏移=RVA-文件偏移
转换关系:VA = ImageBase + RVA
文件偏移地址 = VA – ImageBase – 节偏移 = RVA – 节偏移
0x04 PE文件格式

0x05 DOS头
DOS头在winnt.h
文件中定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| typedef struct _IMAGE_DOS_HEADER { USHORT e_magic; '// DOS签名“MZ-->Mark Zbikowski(设计了DOS的工程师)” -> 4D 5A ' USHORT e_cblp; USHORT e_cp; USHORT e_crlc; USHORT e_cparhdr; USHORT e_minalloc; USHORT e_maxalloc; USHORT e_ss; USHORT e_sp; USHORT e_csum; USHORT e_ip; USHORT e_cs; USHORT e_lfarlc; USHORT e_ovno; USHORT e_res[4]; USHORT e_oemid; USHORT e_oeminfo; USHORT e_res2[10]; LONG e_lfanew; '// 指示NT头的偏移(根据不同文件拥有可变值) -> 00 00 00 C0 -> 192' PE头对于文件的偏移地址 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
|
DOS头 占用64字节
dos头是PE文件结构的第一个头,用于保存对DOS系统的兼容,并且用于定位真正的PE头。图中的B0 00 00 00
也就是e_lfanew
指向了PE头的偏移。
查看e_lfanew
指向位置是否为50 45 00 00
即可判断是否为一个PE文件。
DOS头中4D 5A
到e_lfanew
即B0 00 00 00
中间数据和e_lfanew
到指向PE头偏移位置可有可无。
0x06 DOS_STUB
_IMAGE_DOS_STUB 结构:
该结构未在 winnt.h 中定义,其内容随着链接时使用的链接器不同而不同,通常用于保存在 DOS 环境中的可执行代码。
例如:该结构中的代码用于显示字符串:”This program cannot run in DOS mode”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| typedef struct _IMAGE_NT_HEADERS64 { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER64 OptionalHeader; } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
#ifdef _WIN64 typedef IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS; typedef PIMAGE_NT_HEADERS64 PIMAGE_NT_HEADERS; #else typedef IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS; typedef PIMAGE_NT_HEADERS32 PIMAGE_NT_HEADERS; #endif
|
PE 文件使用 _IMAGE_NT_HEADER 结构提供程序在 Windows 系统中的执行环境。
可以看到 _IMAGE_NT_HEADER 结构针对 32 位和 64 位有不同的定义。
- IMAGE_FILE_HEADER:其中有4个重要的成员,若设置不正确,将会导致文件无法正常运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| typedef struct _IMAGE_FILE_HEADER { WORD Machine; '// 每个CPU拥有唯一的Machine码 -> 4C 01 -> PE -> 兼容32位Intel X86芯片'
WORD NumberOfSections; '// 指文件中存在的节段(又称节区)数量,也就是节表中的项数 -> 00 04 -> 4 // 该值一定要大于0,且当定义的节段数与实际不符时,将发生运行错误。'
DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; '// 指出IMAGE_OPTIONAL_HEADER32结构体的长度。-> 00 E0 -> 224字节 // PE32+格式文件中使用的是IMAGE_OPTIONAL_HEADER64结构体, // 这两个结构体尺寸是不相同的,所以需要在SizeOfOptionalHeader中指明大小。'
WORD Characteristics; '// 标识文件的属性,exe则为010f dll文件则为210e } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; '// 魔数 32位为0x10B,64位为0x20B,ROM镜像为0x107' BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; '// 指出程序最先执行的代码起始地址(RVA) -> 00 00 10 00',对于dll文件来说如果没有入口函数这个值为0,对于驱动该值时初始化函数的地址。 DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase; '// 当加载进内存时,镜像的第1个字节的首选地址。 // WindowEXE默认ImageBase值为00400000,DLL文件的ImageBase值为10000000,也可以指定其他值。 // 执行PE文件时,PE装载器先创建进程,再将文件载入内存, // 然后把EIP寄存器的值设置为ImageBase+AddressOfEntryPoint' '// PE文件的Body部分被划分成若干节段,这些节段储存着不同类别的数据。' DWORD SectionAlignment; '// SectionAlignment指定了节段在内存中的最小单位, -> 00 00 10 00' DWORD FileAlignment; '// FileAlignment指定了节段在磁盘文件中的最小单位,-> 00 00 02 00 // SectionAlignment必须大于或者等于FileAlignment'
WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue;
DWORD SizeOfImage; '// 当镜像被加载进内存时的大小,包括所有的文件头。向上舍入为SectionAlignment的倍数。 // 一般文件大小与加载到内存中的大小是不同的。 -> 00 00 50 00'
DWORD SizeOfHeaders; '// 所有头的总大小,向上舍入为FileAlignment的倍数。 // 可以以此值作为PE文件第一节的文件偏移量。-> 00 00 04 00'
DWORD CheckSum;
WORD Subsystem; '// 运行此镜像所需的子系统 -> 00 02 -> 窗口应用程序 // 用来区分系统驱动文件(*.sys)与普通可执行文件(*.exe,*.dll), // 参考:https://blog.csdn.net/qiming_zhang/article/details/7309909#3.2.3'
WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; '// 指定DataDirectory的数组个数,由于以前发行的Windows NT的原因,它只能为16。 -> 00 00 00 10' IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; '// 数据目录数组。详见下文。' } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_OPTIONAL_HEADER64 { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; ULONGLONG 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; ULONGLONG SizeOfStackReserve; ULONGLONG SizeOfStackCommit; ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
#ifdef _WIN64 typedef IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER; typedef PIMAGE_OPTIONAL_HEADER64 PIMAGE_OPTIONAL_HEADER; #else
|
来看到AddressOfEntryPoint
和ImageBase
,ImageBase
是一个内存的虚拟地址,而AddressOfEntryPoint
是相对虚拟地址。真正可执行文件的虚拟地址是ImageBase
+AddressOfEntryPoint
。但并不是绝对的因为ImageBase
是建议装载地址。如果装载地址已经被使用过了系统会重新分配一块内存。
0x10 数据目录表
1 2 3 4
| typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
|
PE 文件使用 _IMAGE_DATA_DIRECTORY 结构提供 PE 文件中数据结构(输入表、输出表等)的地址和大小信息。
DataDirectory[] 数据目录数组:数组每项都有被定义的值,不同项对应不同数据结构。重点关注的IMPORT和EXPORT,它们是PE头中的非常重要的部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #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信息
|
0x11 导出表:EXPORT_DIRECTORY
一般的简单pe文件并不存在导出表,一般的dll都有导出表,因为写有导出函数可以给到别的pe文件调用功能函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; DWORD AddressOfNames; DWORD AddressOfNameOrdinals; } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
|
0x12 导入表:IMPORT_DESCRIPTOR
1 2 3 4 5 6 7 8 9 10 11
| typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
|
节名称:

参考链接:
PE文件结构解析
DOS头分析