Windows基础知识-PE结构之初始PE
Aking9 发表于 河南 二进制安全 2236浏览 · 2024-07-08 15:28

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文件种类

  • 可执行系列:EXESCR
  • 库系列:`DLLOCXCPLDRV
  • 驱动程序序列:SYSVXD
  • 对象文件系列:OBJ

PE-Struct

PE文件头主要由DOS HeaderDos StubNt HeaderSection 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 HeaderNt 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由多个结构体组合而成,包括SIGNATRUEIMAGE_FILE_HEADERIMAGE_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_HEADER32位,操作系统和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
  • SectionAlignment-0x20:制定了节区在内存中的最小单位
  • FileAlignment-0x24:制定了节区在磁盘文件中的最小单位
  • SizeOfImage-0x38:指定了PE Image在虚拟内存中所占空间的大小
  • SizeOfHeaders-0x3C:指出整个PE头的大小
  • Subsystem-0x44:区分系统驱动文件和普通可执行文件
  • NumberOfRvaAndSize:指定DataDirectory数组的个数
    • 32位程序-0x5C
    • 64位程序-0x6C
  • DataDirectory:由IMAGE_DATA_DIRECTORY结构体组成的数组
    • 32位程序-0x60
    • 64位程序-0x70

程序的真正入口点 = 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;
}

0 条评论
某人
表情
可输入 255