PE结构

PE结构

  • PE结构
1
2
3
4
5
6
7
8
9
10
11
12
什么是PE
MS-DOS
PE头
区段
导入表
导出表
资源表
重定位
TLS
延迟载入
手写最小PE
PE编辑器开发

参考:PE文件结构详解 –(完整版)_pe文件节的个数_擒贼先擒王的博客-CSDN博客

1.什么是PE

PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)

PE文件结构

PE文件格式示意图

2.MS-DOS头结构

image-20230726194030577

各个字段的意义:

第一个字段(mz标致位):e_magic是一个常量,一般都是0x3D5A

最后一个字段(PE头偏移):e_lfanew指向新的PE头,偏移量为0x0138

2.PE标准头结构

1
2
3
4
5
6
7
8
9
10
11
12
//位于0x138
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;

分为32位版本和64位版本
各个字段及其含义:
Signature:PE文件标识,0x4550 0000 DWORD类型,占四字节
FileHeader:文件头
Optional Header:扩展PE头

FileHeader

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

一共有七个字段,分别为:

Machine:运行的平台,可以运行到不同的CPU上,可以用宏代表,用宏与值对比可知运行平台

NumberOfSections:区段数量

常用区段:

  • .text(.code):代码段
  • .data:可读写数据段,用于存放全局变量和静态变量
  • .rdata:只读数据区段
  • .idata:导入数据区段
  • .edata:导出数据区段
  • .rsrc:资源段
  • .bss:未初始化的数据
  • .crt:C++运行库
  • .tls .reloc等等

TimeDateStamp:文件的创建时间(采用格林尼治时间)

PointerToSymbolTable:指向符号表的一个指针,现已不常用

NumberOfSymbols:符号表中符号的数量

SizeOfOptionalHeader:文件头后的扩展头的大小

Characteristics:PE文件的一个属性,可以通过计算得到,一般情况下,普通的EXE文件的值为0x010f,普通dll文件的值为0x0210

OptionalHeader

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
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//

WORD Magic; //文件标识,表示可选头的类型
BYTE MajorLinkerVersion; //链接器的主版本号
BYTE MinorLinkerVersion; //链接器的子版本号
DWORD SizeOfCode; //结构区段的总大小
DWORD SizeOfInitializedData; //已初始化数据块的总大小
DWORD SizeOfUninitializedData; //未初始化数据段的总大小
DWORD AddressOfEntryPoint; //程序入口的一个RVA(注)
DWORD BaseOfCode; //代码段的RVA
DWORD BaseOfData; //数据段的RVA

//
// NT additional fields.
//

DWORD ImageBase; //入口点
DWORD SectionAlignment; //映像文件在装入内存中的区段对其中的大小
DWORD FileAlignment; //映像文件在磁盘中的大小
WORD MajorOperatingSystemVersion; //操作系统最低版本的版本号
WORD MinorOperatingSystemVersion; //操作系统的最低版本的子版本号
WORD MajorImageVersion; //可执行文件的主版本号
WORD MinorImageVersion; //可执行文件的子版本号
WORD MajorSubsystemVersion; //最低子系统的版本号
WORD MinorSubsystemVersion; //最低子系统的子版本号
DWORD Win32VersionValue; //保留值 00000000
DWORD SizeOfImage; //映像文件装入内存后的总大小
DWORD SizeOfHeaders; //MS-DOS头,PE头,驱动表的总大小
DWORD CheckSum; //硬件文件的校验和
WORD Subsystem; //可执行文件所期望的子系统的值
WORD DllCharacteristics; //DLLmain函数何时被调用
DWORD SizeOfStackReserve; //exe文件中被线程所保存的堆栈的大小
DWORD SizeOfStackCommit; //栈的初始化内存大小
DWORD SizeOfHeapReserve; //进程的默认堆的大小
DWORD SizeOfHeapCommit; //每次指派给堆的内存大小
DWORD LoaderFlags; //与调试有关默认为0
DWORD NumberOfRvaAndSizes; //与数据有关的成员数量,一般默认为16个
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;//RVA
DWORD Size;//大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

AddressOfEntryPoint
首先了解相关的一些概念:
VA: Virtual Address 虚拟地址,范围在00000000到FFFFFFF之间 进程的基地址+相对虚拟内存地址
RVA: Reversc Virtual Address 相对虚拟地址
FOA: File Offset Address 文件偏移地址,从文件头计算的地址

区段表结构

1.区段名称及含义

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
.text
默认的代码区块,它的内容全是指令代码,链接器把所有目标文件的text块连接成一个大的.text块,使用Borland C++,编译器产生的代码存放在CODE的区域里
.data
默认的读/写数据块,全局变量,静态变量一般放在这个区段
.rdata
默认只读数据区块,但程序中很少用到该块中的数据,一般两种情况用到,一是MS 的链接器产生EXE文件中用于存放调试目录,二是用于存放说明字符串,如果程序的DEF文件中指定了DESCRIPTION,字符串就会出现在rdata中
.idata
包含其他外来的DLL的函数及数据信息,即输入表,将.idata区块合并成另一个区块已成为一种惯例,典型的是.rdata区块,默认的,链接器只在创建一个Release模式的可执行文件时才能将idata合并到另外一个区块中
.edata
输出表,当创建一个输出API或数据的可执行文件时,连接器会创建一个.EXP文件,这个.EXP文件包含一个.edata区块,其会被加载到可执行文件中,经常被合并到.text.rdata 区块中
.rsrc
资源,包括模块的全部资源,如图标,菜单,位图等,这个区块是只读的,无论如何不应该把它命名为.rsrc以外的名字,也不能合并到其他的区块里
.bss
未初始化的数据,很少在用,取而代之的是执行文件的.data区块的的VirtualSize被扩展大的空间里用来装未初始化的数据.
.crt
用于C++ 运行时(CRT)所添加的数据
.tls
TLS的意思是线程局部存储器,用于支持通过_declspec(thread)声明的线程局部存储变量的数据,这包括数据的初始化值,也包括运行时所需要的额外变量
.reloc
可执行文件的基址重定位,基址重定位一般仅Dll需要的
.sdata
相对于全局指针的可被定位的 短的读写数据
.pdata
异常表,包含CPU特定的IAMGE_RUNTIME_FUNTION_ENTRY结构数组,DataDirectory中的IMAGE_DIRECTORY_ENTRY_EXCEPTION指向它.
.didat
延迟装入输入数据,在非Release模式下可以找到

Name:区段的名称
VirtualSize:区段在内存中的大小
VirtualOffset:区段在内存中的相对虚拟地址RVA
RawSize:区段在文件中的大小
RawOffset:区段在文件中的偏移地址
Characteristics:特征值(下图中我们可以看到.text特征值600000020)

数据目录表结构

导入表

导出表

导出表是用来描述模块(dll)中的导出函数的结构,如果一个模块导出了函数,那么这个函数会被记录在导出表中,这样通过GetProcAddress函数就能动态获取到函数的地址

函数导出的方式有两种:

1.按名字导出 2.按序号导出


PE结构
http://example.com/2023/07/27/PE结构/
作者
luo
发布于
2023年7月27日
许可协议