-
-
[原创]PE结构学习(1)
-
发表于: 2025-5-9 14:51 1100
-
想着把一些远古的学习内容总结一下,于是打算用几篇文章回顾PE结构,也希望能帮助上刚入门的师傅。本人水平有限,如有不足的地方,欢迎指出。本篇文章只讲述PE的大概结构
借助CFF Explorer、010这些工具学习PE结构会方便的多,组成PE的结构体可以在微软的官方文档上找到
CFF Explorer可以直观的给出PE的各个结构以及PE的相关信息,如上图Project.exe是32位程序
010是很好的二进制查看、编辑器,010自带解析PE结构的功能,如果没有可以添加Templates文件。010通过各种bt文件的模板来解析各种结构的文件的
可以看到各种自带的模板,如果不能解析PE的话可以去官网下载对应的bt文件
从上述图中其实就可以发现,这些工具都将PE分成DosHeader、DosStub、NtHeader等部分,595K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8Y4A6Z5i4K6u0V1j5$3&6Q4x3V1k6%4K9h3&6V1L8%4N6K6i4K6u0r3N6$3W2F1x3K6u0Q4x3V1k6V1k6h3u0#2k6#2)9J5c8Y4m8W2i4K6u0V1k6X3!0J5L8h3q4@1中描述了PE的结构,不过比较乱
其实总的来说PE文件就分为这4个部分
对应010中DosHeader与DosStub就是Dos头,NtHeader就是Nt头,SectionHeaders[5]就是节表,上图表示该PE文件有5个节表,也就是有5个节区,SectionHeaders[5]下面的部分就是各个节区
Dos头没有实质的作用,主要是为了保证PE文件在DOS环境下也能有一定的兼容性和可执行性,Nt头是真正意义上的头部,保存了PE文件的各种属性信息,节表描述了对应节区的信息,如大小、位置等,节区是保存程序内容的地方,如程序的代码、各种全局变量。接下来详细学习各个部分
DosHeader部分是一个叫做IMAGE_DOS_HEADER的结构体,定义如下
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // MZ标记 0x5A4D
WORD e_cblp; // 最后(部分)页中的字节数
WORD e_cp; // 文件中的全部和部分页数
WORD e_crlc; // 重定位表中的指针数
WORD e_cparhdr; // 头部尺寸以段落为单位
WORD e_minalloc; // 所需的最小附加段
WORD e_maxalloc; // 所需的最大附加段
WORD e_ss; // 初始的SS值(相对偏移量)
WORD e_sp; // 初始的SP值
WORD e_csum; // 补码校验值
WORD e_ip; // 初始的IP值
WORD e_cs; // 初始的SS值
WORD e_lfarlc; // 重定位表的字节偏移量
WORD e_ovno; // 覆盖号
WORD e_res[4]; // 保留字
WORD e_oemid; // OEM标识符(相对m_oeminfo)
WORD e_oeminfo; // OEM信息
WORD e_res2[10]; // 保留字
LONG e_lfanew; // NT头相对于文件的偏移地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
其大小在64位、32位上都是64个字节,需要特别关注的是第一个与最后一个字段,e_magic字段是标识,e_lfanew字段保存了Nt头的相对文件地址
PE文件开头的两个字节就是MZ
为什么需要单独保存这个字段呢,Dos头后面紧跟着不是Nt头吗。Dos头后面确实紧跟着Nt头,但是Dos头的大小是不确定的,因为DosStub部分是不确定的,该部分的结构体为MS_DOS Stu Program
当前PE文件的DosStub部分大小是A8H
可以看到e_lfanew字段的值为100H,说明PE头从100H开始
一般来说Dos头后面紧跟着Nt头,但是如上图40H+A8H!=100H,不管了,换一个PE好了
这下Dos头后面紧跟着Nt头了
dd1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8Y4A6Z5i4K6u0V1j5$3&6Q4x3V1k6%4K9h3&6V1L8%4N6K6i4K6u0r3N6$3W2F1x3K6u0Q4x3V1k6S2M7r3W2Q4x3V1k6%4K9h3&6F1N6q4)9J5c8X3&6K6i4K6u0V1N6$3W2F1L8Y4c8Q4x3X3c8A6L8h3q4Y4k6g2)9#2k6X3&6@1i4K6g2X3K9r3g2S2k6r3g2J5M7K6j5@1i4@1g2r3i4@1u0o6i4K6R3^5i4@1f1#2i4@1u0q4i4@1q4q4i4@1f1^5i4@1u0p5i4@1q4r3d9f1#2m8c8@1g2Q4y4h3k6z5g2q4)9#2k6V1S2q4b7f1c8q4f1W2x3$3y4q4!0q4y4#2!0n7b7W2)9&6x3#2!0q4y4W2)9&6c8g2)9^5y4q4!0q4y4#2)9&6b7g2)9^5y4q4!0q4y4g2!0m8c8g2)9&6b7g2!0q4y4q4!0n7z5g2)9^5z5g2!0q4c8W2!0n7b7#2)9^5z5b7`.`.
Nt头有两种结构
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;
体现在OptionalHeader字段的不同,本篇就分析64位的了
恒为”PE/0/0”,是PE文件的标识
是一个IMAGE_FILE_HEADER结构,标准PE头,固定20字节,保存PE的一些属性
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //标记可以程序可以运行在什么样的CPU上
WORD NumberOfSections; //记录节的数目
DWORD TimeDateStamp; //时间戳,可以更改
DWORD PointerToSymbolTable; //符号表的偏移量,与debug有关,没有则为零
DWORD NumberOfSymbols; //符号表中的符号数
WORD SizeOfOptionalHeader; //记录MAGE_OPTIONAL_HEADER的大小
WORD Characteristics; //记录文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
d3bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8Y4A6Z5i4K6u0V1j5$3&6Q4x3V1k6%4K9h3&6V1L8%4N6K6i4K6u0r3N6$3W2F1x3K6u0Q4x3V1k6S2M7r3W2Q4x3V1k6%4K9h3&6F1N6q4)9J5c8X3&6K6i4K6u0V1N6$3W2F1L8Y4c8Q4x3X3c8A6L8h3q4Y4k6g2)9#2k6X3k6A6L8r3g2Q4y4h3k6Z5k6h3q4V1k6i4t1`.(微软IMAGE_FILE_HEADER结构的定义)
关注一下SizeOfOptionalHeader记录的是MAGE_OPTIONAL_HEADER,因为MAGE_OPTIONAL_HEADER的大小也是不固定的,NumberOfSections表明节表也就是节区的数量,Characteristics字段表明PE文件的类型,如IMAGE_FILE_DLL(0x2000)表明是DLL文件,IMAGE_FILE_EXECUTABLE_IMAGE(0x0002)表明是EXE可执行文件
图中蓝色部分的第3、4个字节为000007,表明有7个节表,最后两个字节是030F,表明有如下下图的一些性质,其值是下图等value的累和
是一个IMAGE_OPTIONAL_HEADER结构,定义如下
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic; //标志程序是32位还是64位,0B02为64位,0B01为32位
BYTE MajorLinkerVersion; //链接器的主版本号
BYTE MinorLinkerVersion; //链接器次要版本号
DWORD SizeOfCode; //代码节的大小(以字节为单位),如果有多个代码节,则为所有此类节的总和
DWORD SizeOfInitializedData; //初始化的数据节的大小(以字节为单位),如果有多个已初始化的数据节,则为所有此类节的总和
DWORD SizeOfUninitializedData; //未初始化的数据节的大小(以字节为单位),如果有多个未初始化的数据节,则为所有此类节的总和
DWORD AddressOfEntryPoint; //程序入口点,保存的是相对内存偏移地址
DWORD BaseOfCode; //代码段的开头点,也是相对内存的偏移地址
ULONGLONG ImageBase; //内存基址
DWORD SectionAlignment; //内存加载的节对齐大小,头也要对齐
DWORD FileAlignment; //文件对齐大小
WORD MajorOperatingSystemVersion; //所需操作系统的主版本号
WORD MinorOperatingSystemVersion; //所需操作系统的次要版本号
赞赏
- [原创]PE结构学习(3) 685
- [原创]PE结构学习(1) 1101