什么是PE结构
PE结构是Windows操作系统下标准的可执行文件的头格式
当你用二进制的编辑器(如010editor)打开一个exe文件,就会发现这里面有PE结构,其中包括但不限于DOS头
,NT头
,可选NT头
,节头
。
什么是PE结构的文件
PE结构的文件,就是指这个文件他可能是exe
格式,dll
格式,sys
格式的文件,可以在windows系统下双击运行的文件。
基础数据类型
WORD类型 占4个位置,2字节
DWORD类型 占8个位置,4个字节
BYTE类型 占2个位置,1个字节
PE头结构的信息是在<windows.h>
里面,所以可以在vs里面看待相关信息。
DOS头
1 | VS里面原始备注的DOS头格式: |
为了表示每个可执行文件都会具有PE结构,我这里写了一个很简单无意义的程序。
生成为EXE格式后,使用010editor打开,可以发现仍然具有PE结构。
我们开始分析两个重要的DOS头字段意义:
e_magic
Magic number 标志位(MZ 4d5a 只占2字节)
DOS头最明显的标志(e_magic字段)就是它会以它的创世人名字缩写(MZ,二进制表示为4D5A)放在最前面,表示是DOS头。
e_lfanew
File address of new exe header 偏移位置(一般接下来就是文件签名),我们可以理解为标注DOS头结束的位置。接下来就是其他东西了。
这个文件里是40 00 00 00
,表示在这个位置,就是其他东西了
NT头
1 | typedef struct _IMAGE_NT_HEADERS { |
Signature
Signature就是NT头的标志,它的标志是PE..(50 45 00 00)
FileHeader
1 | typedef struct _IMAGE_FILE_HEADER { |
Machine
Machine就是指CPU的架构,程序可以在什么cpu上运行。有以下类型
1 | #define IMAGE_FILE_MACHINE_UNKNOWN 0 |
可以看到在我们文件里,写的是
(64 86)其实应该读为 86 64,这个是小端序的读法。可以看到我们是AMD64(K8)
NumberofSections
NumberofSections是节数,最大支持96。节数是一个EXE文件分成多少个部分。可以看到我们写的是
所以说明有6个区节。我们打开ExeinfoPe
可以看到该文件确实有6个字节。
常见的节列举:
TimeDataStamp
TimeDataStamp 时间戳(UTC 时间) 以格林威治时间来作为起始,来换算的秒数。
PointerToSymbolTbale和NumberOfSymbols
这两个已经被废弃了,所以都默认为0000
SizeOfoptionalHeader
可选头大小,它的主要任务是为了判断该文件运行的环境(对于32位文件来说,它是224,对于64位来说,他是240)
用计算器换算下就是
可见我们是64位文件
Characteristics
Characteristics是文件特征。他是为了区分你是exe文件,还是dll文件。
1 | #define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped |
我们是(00 22)
可以知道是20+2组合而成的
也就是说在这里是可执行文件,并且可引用的地址范围大于2gb(64位程序特有)。
可选NT头
上面的是强制NT头,下面的可选NT头是可写可不写的。(虽然这些并不是你想写都写,是编译器帮你写的。可选的意思是如果写了就有详细的数字,没有就是0000)
1 | typedef struct _IMAGE_OPTIONAL_HEADER { |
Magic:
Magic文件类型标识
1 | 0x10B表明这是一个32位镜像文件。 |
可以看到我们这里是020b再次确定了我们文件是64位。
MajorLinkerVersion
链接器主版本号
MinorLinkerVersion
链接器副版本号
SizeofCode
SizeofCode表示text的总大小
SizeofIntializedData
表示data总大小(变量)
00 1e 00 00
SizeofUnintializedData
表示BSS总大小
00 00 00 00
bss:bss段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0.
下面三个是以偏移量(表示的不是绝对数值,而是使用加法)表示:
AddressofEntryPoint
程序的虚拟入口地址 (58 12 00 00)
virtual address:
加载进去的第一个二进制地址
reverse virtual address
加载进去的第一个二进制的地址-.text的地址
BaseOfCode
程序的text段第一行地址是:
代码基址 (00 10 00 00)
BaseOfData
数据基址(00 02 00 00)
ImageBase
镜像基址,当加载进内存时镜像的第1个字节的首选地址。它必须是64k的倍数,DLL默认是10000000H. Windows CE的EXE默认是 00010000H。 Windows 系列的EXE默认是 00400000H。
(ps:ImageBase+BaseOfcode=程序的开始代码地址)。
这几个要做重点说明:
AddressofEntryPoint是指程序运行时,进去的第一个地址。这里我们可以使用od来看。
当用od调试程序的时候会发现程序一进去就会在1258这个地址
ImageBase是一个固定值。在windows系列的exe默认是 00400000H。
只需要记得imagebase+baseofcode=程序开始时的地址。程序开始时的地址不是指入口地址。而是指在od中程序的第一行地址。
SectionAligment(不重要)
当加载进内存时节的对齐值(以字节计) (内存)
FileAlignment(不重要)
用来对齐镜像文件的节中的原始数据的对齐因子(以字节计)(内存)
版本号
1 | WORD MajorOperatingSystemVersion; |
以上字段全是版本号
Win32VersionValue
表示win32版本(必须为0)
SizeofImage
总大小
SizeofHeaders
头的总大小
CheckSum
校验和(内容长度和内容本身做某种运算)
子系统
子系统表
表示是一个windows命令行启动的程序
Dllcharacteristics
dll特征
1 | #define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 // DLL can move. aslr随 |
这里的第一个DLL特征比较重要,aslr
是windows7以后的一个windows系统保护机制,他会在程序启动后随机分配内存地址。
我们这里是8160(8000+100+40+20)表示可以关掉它。
SizeOfStackReserve
栈保留大小(我想要多少个栈)
SizeofStackCommit
栈申请大小(我实际拿到了好多个栈)
SizeofHeapReserve
堆保留大小
SizeofHeapCommit
堆申请大小
LoaderFlag
标志位必须为0
NumberOFRvaAndSizes
数据目录
节头
一个EXE文件会被分成很多个部分,其中常见的有.text
,.rdata
,.data
,.rsrc
,.reloc
部分
它们分别有以下作用:
.text
:也被称为代码段,实际运行功能代码的段落
.rdata
,.data
:存放变量的位置
.rsrc
:存放静态资源(软件图标,软件背景音乐,软件图片等资源)
.reloc
:重定位字段,表示函数定位的一些东西
在每一个部分中都会有分别的对应的PE结构,因为我们也叫他称为节头
1 | typedef struct _IMAGE_SECTION_HEADER { |
name
本节的名字
二选一:
PhysialAddress
本节的物理地址
VirtualSize
本节的实际大小
VirtualAddress
本节的RVA
SizeOfRawData
本节在磁盘中的大小(00 0E 00 00)
PointerToRawData
本节在磁盘中的偏移(00 04 00 00)
PointerToRelocations
在exe文件中无意义(00 00 00 00)
PointerToLinenumbers
行号表的位置(调试用,不过基本没用)(00 00 00 00)
NumberOFrelocations
重定位数量 (00 00)
NumberOFLinenumbers
行号表数量(00 00)
Characteristics
我们是60000020(40000000+20000000+20)
表示文件可读可执行。
另一种方法观察PE头
除了使用010editor来看,我们也可以使用python自动化看pe头
1 | import pefile |
这是一个总览,我们还可以分别看想要的属性。比如
1 | i =pe.FILE_HEADER.NumberOfSections |
这里给出自己写的脚本用来做参考:
1 | import pefile |