PE-前置知识
可执行文件(executable file)指的是可以由操作系统进行加载执行的文件。
- Windows操作系统下,可执行文件格式PE(Portable Executable)可以是“.exe文件”、“.sys文件”、“.com文件”等类型文件。
- Linux操作系统下,可执行文件格式为ELF即Executable and Linkable Format.
PE
-Portable Executable
-可移植可执行
文件格式是Windows可执行文件、对象代码和DLL所使用的标准格式。由COFF(UNIX平台下的通用对象文件格式)格式文件发展而来。32位成为PE32,64位称为PE+或PE32+。
PE文件种类
- 可执行系列:
EXE
、SCR
- 库系列:
`DLL
、OCX
、CPL
、DRV
- 驱动程序序列:
SYS
、VXD
- 对象文件系列:
OBJ
PE-Struct
PE文件头主要由DOS Header
、Dos Stub
、Nt Header
和Section Header
组成
DOS
DOS Header
主要为现代PE文件可以对早期的DOS文件进行良好兼容存在
PE文件的第一个字节位于一个传统的MS-DOS头部,称作IMAGE_DOS_HEADER
https://www.vergiliusproject.com/kernels/x64/windows-11/23h2/_IMAGE_DOS_HEADER
//0x40 bytes (sizeof)
struct _IMAGE_DOS_HEADER
{
USHORT e_magic; //0x0
USHORT e_cblp; //0x2
USHORT e_cp; //0x4
USHORT e_crlc; //0x6
USHORT e_cparhdr; //0x8
USHORT e_minalloc; //0xa
USHORT e_maxalloc; //0xc
USHORT e_ss; //0xe
USHORT e_sp; //0x10
USHORT e_csum; //0x12
USHORT e_ip; //0x14
USHORT e_cs; //0x16
USHORT e_lfarlc; //0x18
USHORT e_ovno; //0x1a
USHORT e_res[4]; //0x1c
USHORT e_oemid; //0x24
USHORT e_oeminfo; //0x26
USHORT e_res2[10]; //0x28
LONG e_lfanew; //0x3c
};
DOS部分我们需要熟悉的是e_magic
成员和e_lfanew
成员,也是判断是否为PE文件的条件
-
e_magic
:DOS签名(4D5A,MZ) -
e_lfanew
:指示NT头的偏移(文件不同,值不同),这个偏移地址的起始内容是否为50 45
,即PE
。
DOS头-MZ头的结构和大小是固定的,在C++中会将e_magic
给预定义为IMAGE_DOS_SIGNATURE
#include<iostream>
#include<Windows.h>
int main()
{
std::cout << sizeof(IMAGE_DOS_HEADER)<< std::endl; //64
std::cout <<std::hex<<std::uppercase << IMAGE_DOS_SIGNATURE << std::endl; //5A4D
//#define IMAGE_DOS_SIGNATURE 0x5A4D
// MZ输出的是5A4D,是因为在计算机上存储的是小端存储
}
DOS Stub
DOS块的部分Dos Header
和 Nt Header
中间的部分,这部分是由链接器所写入的,可以随意进行修改,并不影响程序的运行
PE头-Nt Header
Nt Header结构体根据操作系统不同,会有不同的变化,有些资料上也叫PE头。
-
IMAGE_NT_HEADERS
:Windows 32位https://www.vergiliusproject.com/kernels/x86/windows-10/22h2/_IMAGE_NT_HEADERS
//0xf8 bytes (sizeof) struct _IMAGE_NT_HEADERS { ULONG Signature; //0x0 struct _IMAGE_FILE_HEADER FileHeader; //0x4 struct _IMAGE_OPTIONAL_HEADER OptionalHeader; //0x18 };
-
IMAGE_NT_HEADERS64
:Windows 64位https://www.vergiliusproject.com/kernels/x64/windows-10/22h2/_IMAGE_NT_HEADERS64
//0x108 bytes (sizeof) struct _IMAGE_NT_HEADERS64 { ULONG Signature; //0x0 struct _IMAGE_FILE_HEADER FileHeader; //0x4 struct _IMAGE_OPTIONAL_HEADER64 OptionalHeader; //0x18 };
IMAGE_NT_HEADERS
由多个结构体组合而成,包括SIGNATRUE
,IMAGE_FILE_HEADER
和 IMAGE_OPTIONAL_HEADER
三部分。NT头部在PE文件中的位置不是固定不变的,NT头部的位置由DOS头部的e_lfanew
字段给出。
Signature
当执行体在支持PE文件结构的操作系统中执行时,PE装载器将从IMAGE_DOS_HEADER
结构的e_lfanew
字段里找到NT头的起始偏移量,用其加上基址,得到PE文件头的指针。
在C++中Signature
会被预处理为IMAGE_NT_SIGNATURE
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
FileHeader
文件头,表现文件大致属性,结构体为IMAGE_FILE_HEADER
32位,操作系统和64位操作系统的_IMAGE_FILE_HEADER
是一样的.
https://www.vergiliusproject.com/kernels/x64/windows-10/22h2/_IMAGE_FILE_HEADER
https://www.vergiliusproject.com/kernels/x86/windows-10/22h2/_IMAGE_FILE_HEADER
//0x14 bytes (sizeof)
struct _IMAGE_FILE_HEADER
{
USHORT Machine; //0x0
USHORT NumberOfSections; //0x2
ULONG TimeDateStamp; //0x4
ULONG PointerToSymbolTable; //0x8
ULONG NumberOfSymbols; //0xc
USHORT SizeOfOptionalHeader; //0x10
USHORT Characteristics; //0x12
};
重要成员有4个
-
Machine
:每个CPU都拥有的唯一的Machine码,兼容32位Intel x86芯片的Machine码为14C
; -
NumberOfSections
:指出文件中存在的节区数量; -
SizeOfOptionalHeader
:- 指出结构体IMAGE_OPTIONAL_HEADER32(32位系统)的长度
-
Characteristics
:标识文件属性,文件是否是可运行形态、是否为DLL等,以bit OR形式进行组合
OptionalHeader
_IMAGE_OPTIONAL_HEADER
https://www.vergiliusproject.com/kernels/x86/windows-10/22h2/_IMAGE_OPTIONAL_HEADER
32位操作系统的_IMAGE_OPTIONAL_HEADER
-0xe0 bytes (sizeof)
-224个字节
结构体详情
//0xe0 bytes (sizeof)
struct _IMAGE_OPTIONAL_HEADER
{
// Standard fields.
USHORT Magic; //0x0
UCHAR MajorLinkerVersion; //0x2
UCHAR MinorLinkerVersion; //0x3
ULONG SizeOfCode; //0x4
ULONG SizeOfInitializedData; //0x8
ULONG SizeOfUninitializedData; //0xc
ULONG AddressOfEntryPoint; //0x10
ULONG BaseOfCode; //0x14
ULONG BaseOfData; //0x18
// NT additional fields.
ULONG ImageBase; //0x1c
ULONG SectionAlignment; //0x20
ULONG FileAlignment; //0x24
USHORT MajorOperatingSystemVersion; //0x28
USHORT MinorOperatingSystemVersion; //0x2a
USHORT MajorImageVersion; //0x2c
USHORT MinorImageVersion; //0x2e
USHORT MajorSubsystemVersion; //0x30
USHORT MinorSubsystemVersion; //0x32
ULONG Win32VersionValue; //0x34
ULONG SizeOfImage; //0x38
ULONG SizeOfHeaders; //0x3c
ULONG CheckSum; //0x40
USHORT Subsystem; //0x44
USHORT DllCharacteristics; //0x46
ULONG SizeOfStackReserve; //0x48
ULONG SizeOfStackCommit; //0x4c
ULONG SizeOfHeapReserve; //0x50
ULONG SizeOfHeapCommit; //0x54
ULONG LoaderFlags; //0x58
ULONG NumberOfRvaAndSizes; //0x5c
struct _IMAGE_DATA_DIRECTORY DataDirectory[16]; //0x60
};
_IMAGE_OPTIONAL_HEADER64
https://www.vergiliusproject.com/kernels/x64/windows-10/22h2/_IMAGE_OPTIONAL_HEADER64
64位操作系统的_IMAGE_OPTIONAL_HEADER64
-xf0 bytes (sizeof)
-240个字节
结构体详情
//0xf0 bytes (sizeof)
struct _IMAGE_OPTIONAL_HEADER64
{
// Standard fields.
USHORT Magic; //0x0
UCHAR MajorLinkerVersion; //0x2
UCHAR MinorLinkerVersion; //0x3
ULONG SizeOfCode; //0x4
ULONG SizeOfInitializedData; //0x8
ULONG SizeOfUninitializedData; //0xc
ULONG AddressOfEntryPoint; //0x10
ULONG BaseOfCode; //0x14
// NT additional fields.
ULONGLONG ImageBase; //0x18
ULONG SectionAlignment; //0x20
ULONG FileAlignment; //0x24
USHORT MajorOperatingSystemVersion; //0x28
USHORT MinorOperatingSystemVersion; //0x2a
USHORT MajorImageVersion; //0x2c
USHORT MinorImageVersion; //0x2e
USHORT MajorSubsystemVersion; //0x30
USHORT MinorSubsystemVersion; //0x32
ULONG Win32VersionValue; //0x34
ULONG SizeOfImage; //0x38
ULONG SizeOfHeaders; //0x3c
ULONG CheckSum; //0x40
USHORT Subsystem; //0x44
USHORT DllCharacteristics; //0x46
ULONGLONG SizeOfStackReserve; //0x48
ULONGLONG SizeOfStackCommit; //0x50
ULONGLONG SizeOfHeapReserve; //0x58
ULONGLONG SizeOfHeapCommit; //0x60
ULONG LoaderFlags; //0x68
ULONG NumberOfRvaAndSizes; //0x6c
struct _IMAGE_DATA_DIRECTORY DataDirectory[16]; //0x70
};
OptionalHeader
重要成员
成员-结构体内的位置
-
Magic
-0x00
:IMAGE_OPTIONAL_HEADER32为10B
,IMAGE_OPTIONAL_HEADER64为20B
-
AddressOfEntryPoint
-0x10
:持有EP的RVA值,指出程序最先执行的代码起始地址 -
ImageBase
:指出文件的优先装入地址- 32位进程-
0x1C
,虚拟内存范围为:0~7FFFFFFF - 64位进程-
0x18
,
- 32位进程-
-
SectionAlignment
-0x20
:制定了节区在内存中的最小单位 -
FileAlignment
-0x24
:制定了节区在磁盘文件中的最小单位 -
SizeOfImage
-0x38
:指定了PE Image在虚拟内存中所占空间的大小 -
SizeOfHeaders
-0x3C
:指出整个PE头的大小 -
Subsystem
-0x44
:区分系统驱动文件和普通可执行文件 -
NumberOfRvaAndSize
:指定DataDirectory数组的个数- 32位程序-
0x5C
- 64位程序-
0x6C
- 32位程序-
-
DataDirectory
:由IMAGE_DATA_DIRECTORY
结构体组成的数组- 32位程序-
0x60
- 64位程序-
0x70
- 32位程序-
程序的真正入口点 = ImageBase + AddressOfEntryPoint
节区头-Section Header
节区头中定义了各节区的属性,包括不同的特性、访问权限等,结构体为IMAGE_SECTION_HEADER
https://www.vergiliusproject.com/kernels/x64/windows-10/22h2/_IMAGE_SECTION_HEADER
https://www.vergiliusproject.com/kernels/x86/windows-10/22h2/_IMAGE_SECTION_HEADER
节表不止一个,可能有多个,节表的数量存在标准PE头中的NumberOfSections属性,CRACCKME.EXE
有6个节表
节表的结构如下,整体为40个字节
//0x28 bytes (sizeof)
struct _IMAGE_SECTION_HEADER
{
UCHAR Name[8]; //0x0
union
{
ULONG PhysicalAddress; //0x8
ULONG VirtualSize; //0x8
} Misc; //0x8
ULONG VirtualAddress; //0xc
ULONG SizeOfRawData; //0x10
ULONG PointerToRawData; //0x14
ULONG PointerToRelocations; //0x18
ULONG PointerToLinenumbers; //0x1c
USHORT NumberOfRelocations; //0x20
USHORT NumberOfLinenumbers; //0x22
ULONG Characteristics; //0x24
};
重要成员:
-
VirtualSize
-0x08
:内存中节区所占大小 -
VirtualAddress
-0x0C
:内存中节区起始地址(RVA) -
SizeOfRawData
-0x10
:磁盘文件中节区所占大小 -
Charateristics
-0x24
:节区属性(bit OR)
PE头信息读取
#define WINDOWS_IGNORE_PACKING_MISMATCH
#include <iostream>
#include <fstream>
#include <vector>
#include <windows.h>
void CoutPEHeaderInformation(const std::string& filename);
LPVOID ReadPEFile(const std::string& filename);
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "请输入PE文件名" << std::endl;
return 1;
}
std::string filename = argv[1];
CoutPEHeaderInformation(filename);
return 0;
}
// 打印节头信息
void CoutPEHeaderInformation(const std::string& filename) {
LPVOID pFileBuffer = nullptr;
PIMAGE_DOS_HEADER pDosHeader = nullptr;
PIMAGE_NT_HEADERS32 pNTHeader32 = nullptr;
PIMAGE_FILE_HEADER pFileHeader = nullptr;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader32 = nullptr;
PIMAGE_SECTION_HEADER pSectionHeader = nullptr;
DWORD dSectionsCounts = 0;
// 读取PE文件
pFileBuffer = ReadPEFile(filename);
if (!pFileBuffer) {
std::cerr << "读取文件失败" << std::endl;
return;
}
// 检查文件是否有有效的MZ标志
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE) {
std::cerr << "不是有效的MZ标志" << std::endl;
delete[] static_cast<char*>(pFileBuffer);
return;
}
// 获取DOS头
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
std::cout << "**********DOSHeader*********" << std::endl;
std::cout << "DOSHeader-e_magic: " << std::hex << pDosHeader->e_magic << std::endl;
std::cout << "DOSHeader-e_lfanew: " << std::hex << pDosHeader->e_lfanew << std::endl << std::endl;
// 检查文件是否有有效的PE标志
if (*((PWORD)((DWORD_PTR)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE) {
std::cerr << "不是有效的PE标志" << std::endl;
delete[] static_cast<char*>(pFileBuffer);
return;
}
// 获取NT头
pNTHeader32 = (PIMAGE_NT_HEADERS32)((DWORD_PTR)pFileBuffer + pDosHeader->e_lfanew);
std::cout << "**********NTHeader**********" << std::endl;
std::cout << "NTHeader-Signature: " << std::hex << pNTHeader32->Signature << std::endl << std::endl;
// 获取PE头
pFileHeader = &pNTHeader32->FileHeader;
std::cout << "**********PEFileHeader**********" << std::endl;
std::cout << "PEFileHeader-Machine: " << std::hex << pFileHeader->Machine << std::endl;
std::cout << "PEFileHeader-NumberOfSections: " << std::hex << pFileHeader->NumberOfSections << std::endl;
std::cout << "PEFileHeader-SizeOfOptionalHeader:" << std::hex << pFileHeader->SizeOfOptionalHeader << std::endl << std::endl;
dSectionsCounts = pFileHeader->NumberOfSections;
// 获取可选头
pOptionHeader32 = &pNTHeader32->OptionalHeader;
std::cout << "********PEOptionHeader32*********" << std::endl;
std::cout << "PEOptionHeader32-Magic: " << std::hex << pOptionHeader32->Magic << std::endl;
std::cout << "PEOptionHeader32-AddressOfEntryPoint: " << std::hex << pOptionHeader32->AddressOfEntryPoint << std::endl;
std::cout << "PEOptionHeader32-ImageBase: " << std::hex << pOptionHeader32->ImageBase << std::endl;
std::cout << "PEOptionHeader32-SizeOfHeaders: " << std::hex << pOptionHeader32->SizeOfHeaders << std::endl;
std::cout << "PEOptionHeader32-Subsystem: " << std::hex << pOptionHeader32->Subsystem << std::endl << std::endl;
// 获取节头
pSectionHeader = (PIMAGE_SECTION_HEADER)(((DWORD_PTR)pOptionHeader32) + pFileHeader->SizeOfOptionalHeader);
std::cout << "**********SectionHeader**********" << std::endl;
// 打印每个节头的信息
for (DWORD i = 0; i < dSectionsCounts; i++, pSectionHeader++) {
std::cout << "Section[" << (i) << "] 名字: " << reinterpret_cast<char*>(pSectionHeader->Name) << std::endl;
std::cout << "Section[" << (i) << "]-VirtualSize: " << std::hex << pSectionHeader->Misc.VirtualSize << std::endl;
std::cout << "Section[" << (i) << "]-VirtualAddress: " << std::hex << pSectionHeader->VirtualAddress << std::endl;
std::cout << "Section[" << (i) << "]-SizeOfRawData: " << std::hex << pSectionHeader->SizeOfRawData << std::endl;
std::cout << "Section[" << (i) << "]-PointerToRawData: " << std::hex << pSectionHeader->PointerToRawData << std::endl;
std::cout << "Section[" << (i) << "]-Characteristics: " << std::hex << pSectionHeader->Characteristics << std::endl << std::endl;
}
// 释放分配的内存
delete[] static_cast<char*>(pFileBuffer);
}
// 读取PE文件并返回其内容的指针
LPVOID ReadPEFile(const std::string& filename) {
std::ifstream filePE(filename, std::ios::binary | std::ios::ate);
if (!filePE.is_open()) {
std::cerr << "无法打开exe文件" << std::endl;
return nullptr;
}
std::streamsize size = filePE.tellg();
filePE.seekg(0, std::ios::beg);
std::vector<char> buffer(size);
if (!filePE.read(buffer.data(), size)) {
std::cerr << "读取数据失败" << std::endl;
filePE.close();
return nullptr;
}
LPVOID pFileBuffer = new char[size];
if (!pFileBuffer) {
std::cerr << "内存分配失败" << std::endl;
filePE.close();
return nullptr;
}
memcpy(pFileBuffer, buffer.data(), size);
return pFileBuffer;
}