Java教程

PE(一)

本文主要是介绍PE(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

PE结构

一览图

  • PE磁盘文件与内存镜像结构图

DOS/NT头结构

DOS头结构

  • PE文件中的DOS部分由MZ格式的文件头和可执行代码部分组成,可执行代码被称为DOS块(DOS stub),MZ格式的文件头由IMAGE_DOS_HEADER结构定义,在C语言头文件winnt.h中有对这个DOS结构详细定义,如下所示

    typedef struct _IMAGE_DOS_HEADER { 
        WORD   e_magic;                     // DOS的头部
        WORD   e_cblp;                      // Bytes on last page of file
        WORD   e_cp;                        // Pages in file
        WORD   e_crlc;                      // Relocations
        WORD   e_cparhdr;                   // Size of header in paragraphs
        WORD   e_minalloc;                  // Minimum extra paragraphs needed
        WORD   e_maxalloc;                  // Maximum extra paragraphs needed
        WORD   e_ss;                        // Initial (relative) SS value
        WORD   e_sp;                        // Initial SP value
        WORD   e_csum;                      // Checksum
        WORD   e_ip;                        // Initial IP value
        WORD   e_cs;                        // Initial (relative) CS value
        WORD   e_lfarlc;                    // File address of relocation table
        WORD   e_ovno;                      // Overlay number
        WORD   e_res[4];                    // Reserved words
        WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
        WORD   e_oeminfo;                   // OEM information; e_oemid specific
        WORD   e_res2[10];                  // Reserved words
        LONG   e_lfanew;                    // 指向了PE文件的开头(重要)
      } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
    

    在DOS文件头中,第一个字段e_magic被定义为MZ,标志着DOS文件的开头部分,最后一个字段e_lfanew则指明了PE文件的开头位置,现在来说除了第一个字段和最后一个字段有些用处,其他字段几乎已经废弃了

    DOS头的大小是固定的,其大小为0x3Ch,以winXPnotepad.exe为例子解析如下

PE头结构

  • 从DOS文件头的位置向下偏移e_lfanew字段的值的位置,就是真正的PE文件头的位置,该文件头是由IMAGE_NT_HEADERS结构定义的,定义结构如下
typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;                   // PE文件标识字符
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

根据DOS解析的例子,在0x3Ch开始的DWORD中存储着e_lfanew字段的值,其值指明了PE头相对于文件的偏移,用于定位PE文件

PE文件头的第一个DWORD是一个标志,默认情况下它被定义为00004550h也就是P,E两个字符另外加上两个零,而大部分的文件属性由标志后面的IMAGE_FILE_HEADERIMAGE_OPTIONAL_HEADER32结构来定义

跟进IMAGE_FILE_HEADER

IMAGE_FILE_HEADER的结构定义如下

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;                  // 运行平台
    WORD    NumberOfSections;         // 文件的节数目
    DWORD   TimeDateStamp;            // 文件创建日期和时间
    DWORD   PointerToSymbolTable;     // 指向符号表(用于调试)
    DWORD   NumberOfSymbols;          // 符号表中的符号数量
    WORD    SizeOfOptionalHeader;     // IMAGE_OPTIONAL_HANDLER32结构的长度
    WORD    Characteristics;          // 文件的属性 exe=010fh dll=210eh
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

跟进IMAGE_OPTIONAL_HEADER32

IMAGE_OPTIONAL_HEADER32的结构定义如下

typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD    Magic;
    BYTE    MajorLinkerVersion;           // 连接器版本
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;                   // 所有包含代码节的总大小
    DWORD   SizeOfInitializedData;        // 所有已初始化数据的节总大小
    DWORD   SizeOfUninitializedData;      // 所有未初始化数据的节总大小
    DWORD   AddressOfEntryPoint;          // 程序执行入口RVA
    DWORD   BaseOfCode;                   // 代码节的起始RVA
    DWORD   BaseOfData;                   // 数据节的起始RVA
    DWORD   ImageBase;                    // 程序镜像基地址
    DWORD   SectionAlignment;             // 内存中节的对其粒度
    DWORD   FileAlignment;                // 文件中节的对其粒度
    WORD    MajorOperatingSystemVersion;  // 操作系统主版本号
    WORD    MinorOperatingSystemVersion;  // 操作系统副版本号
    WORD    MajorImageVersion;            // 可运行于操作系统的最小版本号
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;        // 可运行于操作系统的最小子版本号
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;                  // 内存中整个PE映像尺寸
    DWORD   SizeOfHeaders;                // 所有头加节表的大小
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;           // 初始化时堆栈大小
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;          // 数据目录的结构数量
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

IMAGE_DATA_DIRECTORY数据目录列表,它由16个相同的IMAGE_DATA_DIRECTORY结构组成,这16个数据目录结构定义很简单仅仅指出了某种数据的位置和长度,定义如下

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;      // 数据起始RVA
    DWORD   Size;                // 数据块的长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

可执行文件不同阶段的存放方式

在执行一个可执行文件时,首先将文件从磁盘原样Copy到内存中,这段存储着和磁盘相同数据的内存称作FileBuffer。只是简单的Copy到内存中并不能够执行这个文件,想要运行它,我们还需要对它进行”拉伸“,磁盘存储的方式和运行时的存储方式的对齐大小可能不一样,在磁盘存放的对齐方式可能导致基准地址+偏移地址的方式并不能正确的找到地址(类似编译器在生成可执行文件时会对其进行压缩,压缩后还以运行时的寻址方式显然不能正确寻址),所以需要通过“拉伸”使文件恢复到可以执行的状态

在磁盘和FileBuffer时其值完全相同

大致结构如下

在ImageBuffer的存放方式

  • 存放拉伸后的那段内存称为ImageBuffer
  • 改变FileBuffer的对齐大小,再存入内存中
  • 对于起始地址,ImageBuffer的起始地址称作ImageBase

这篇关于PE(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!