PE文件结构

可执行文件:可以由操作系统进行加载执行的文件

在Windows平台中若要程序可执行,必须遵守可执行文件的格式

可执行文件的格式:

Windows平台:PE(Portable Executable)文件结构,其中Portable也就是Windows系统中能够跨平台运行

Linux平台:ELF(Executable and Linking Format)文件结构


PE文件特征:

如exe、dll、vxd、sys和vdm等文件都遵守可执行文件的格式!

常用的识别PE文件的方法:

1、用HEX编辑器打开文件,头两个字节是不是MZ,然后向后到第64字节的位置,例如为10 01 00 00,因为存储模式为小端存储所以,实际是0x00000110

2、再去找到0x00000110的位置,向后2字节,如果为字符显示为PE则基本就是PE文件了


PE文件结构:

PE文件结构分为四大部分:

1、Dos部分 2、PE文件头 3、节表 4、节数据


1、Dos部分:

MZ文件头IMAGE_DOS_HEADER结构体,其大小占64个字节,并且该结构中的最后一个LONG类型e_lfanew成员指向PE文件头的位置为中的PE文件头标志的地址

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    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;                    // File address of new exe header 指向PE文件头的位置为中的PE文件头标志的地址
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

Dos Stub:属于链接器进行填充的,大小不一定,属于DOS部分的DOS块,无实际作用,但是可以作为注入手段进行利用!图中第二个红框则是Dos Stub


2、PE文件头:

整个PE头的结构体

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;  //PE文件头标志
    IMAGE_FILE_HEADER FileHeader; //PE文件表头
    IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展PE头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

PE文件头分为三部分:

1、PE文件头标志(PE标识),占4个字节,作为标识用,如上图查看是否为PE文件就是利用这个方法!

2、PE文件表头(标准PE头),占20个字节,为IMAGE_FILE_HEADER结构体,该结构体中SizeOfOptionalHeader结构体中有个成员可以看出可扩展文件表头的大小

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader; // 可以看出可扩展文件表头的大小,也分为x32 x64 x32大小为E0 x64大小为F0
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

3、可扩展文件表头(扩展PE头),占224字节,结构体分两种 一种是32位的,一种是16位的,分别是IMAGE_OPTIONAL_HEADER32结构体 和 16xIMAGE_DATA_DIRECTORY结构体

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;  // 分辨32位程序还是64位,如果32位则10B,64位则20B
    BYTE    MajorLinkerVersion; //链接器版本号
    BYTE    MinorLinkerVersion; //链接器版本号
    DWORD   SizeOfCode; //所有代码节的总和 文件对齐后的大小 编译器填写的,无用处
    DWORD   SizeOfInitializedData; //已经初始化数据的节的总大小 文件对齐后的大小   编译器填写的,无用处
    DWORD   SizeOfUninitializedData; // 未初始化数据的节的总大小 文件对齐后的大小   编译器填写的,无用处
    DWORD   AddressOfEntryPoint; // 程序入口
    DWORD   BaseOfCode; //代码开始的基址 编译器填写的,无用处
    DWORD   BaseOfData; //数据开始的基址  编译器填写的,无用处

    //
    // NT additional fields.
    //

    DWORD   ImageBase; //内存镜像基址
    DWORD   SectionAlignment; //内存对齐
    DWORD   FileAlignment; //文件对齐
    WORD    MajorOperatingSystemVersion; //操作系统版本号
    WORD    MinorOperatingSystemVersion; //操作系统版本号
    WORD    MajorImageVersion; //PE文件自身的版本号
    WORD    MinorImageVersion; //PE文件自身的版本号
    WORD    MajorSubsystemVersion; //运行所需要子系统的版本号
    WORD    MinorSubsystemVersion; //运行所需要子系统的版本号
    DWORD   Win32VersionValue; //子系统版本的值,必须为0
    DWORD   SizeOfImage; //内存中整个PE文件的映射尺寸,比实际的值大,必须是SectionAlignment整数倍
    DWORD   SizeOfHeaders; //所有的头+节表按照文件对齐后的大小
    DWORD   CheckSum; //校验和,可伪造
    WORD    Subsystem; //子系统, 驱动程序(1) 图形界面(2) DLL(3)
    WORD    DllCharacteristics;	 //文件特性 不是针对DLL文件的
    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;

其中FileAlignment成员的值为文件对齐之后的大小,SizeOfHeaders成员的值一定是FileAlignment的值的整数倍,而且SizeOfHeaders保存的值是所有的头(有DOS部分 PE文件头)加上节表的大小

如果FileAlignment(文件对齐之后的大小)是200 那么SizeOfHeaders头一定是200的整数倍,那么就是400,如果FileAlignment不到200那么SizeOfHeaders就按照200对齐,如下图可以看到文件对齐后的FileAlignment的大小为0x200H,SectionAlignment是在内存中加载的时候,文件对齐之后的大小,在FileAlignment前四个字节,那么就是0x1000H

那么再去看下SizeOfHeaders的大小,相差二十个字节,继续往后移,结果如下:0x400,的确是FileAlignment的两倍

如果每次都是整数倍,那肯定有很多地方空出来,实际上是用空间换取时间,增加了处理效率,具体以后自己知道了再补上!


3、节表: 其中每个节大小占40个字节,十六进制 0x28

IMAGE_SECTION_HEADER结构体(40字节) * 节的数量(大小取决于节的数量)

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress; //当前节的名称 , 占8字节
            DWORD   VirtualSize; //内存中文件对齐大小
    } Misc;
    DWORD   VirtualAddress; //在内存中的偏移地址
    DWORD   SizeOfRawData; //当前节在文件中对齐后的大小
    DWORD   PointerToRawData; //当前节在文件中的偏移
    DWORD   PointerToRelocations; // 调试相关
    DWORD   PointerToLinenumbers;// 调试相关
    WORD    NumberOfRelocations;// 调试相关
    WORD    NumberOfLinenumbers;// 调试相关
    DWORD   Characteristics; //文件属性,比如该节数据属性是否为可执行属性,都在这里面
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

我这边有五个节,如下显示:


4、节数据:

sizeofHeaders 按照文件对齐.存储着 头 + 节表的大小,这里就是0x400之后的数据了!也就是如下:

在硬盘中存储的时候文件的每个节数据之间的大小由FileAlignment决定!

上面都是空的,同样可以进行非法利用!

posted @ 2020-02-14 17:24  zpchcbd  阅读(982)  评论(0编辑  收藏  举报