标题:PE结构详解


 PE文件

    如图1所示,一个完整的PEPortable Executable)文件由DOS头,PE文件头,块表,块和调试信息(调试版本有效)组成。

 

1 PE文件格式

DOS头部,以e_magic开头,它的值是固定的”0x5a4d”,即(MZ)开头。最开头的是部分是DOS部首,DOS部首由两部分组成:DOSMZ文件标志和DOS stub(DOS存根程序)。之所以设置DOS部首是微软为了兼容原有的DOS系统下的程序而设立的。

 

DOS Stub即为一句话“This program Cannot be run in DOS mode”。

 

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

   } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

DOS头部,有一个结构成员e_lfanew指向了真正的PE头。值得注意的是PE文件头中的IMAGE_OPTIONAL_HEADER32是一个非常重要的结构,PE文件中的导入表、导出表、资源、重定位表等数据的位置和长度都保存在这个结构里。

 

typedef struct _IMAGE_NT_HEADERS {

    DWORD Signature; //PE头签名PE\0\0

    IMAGE_FILE_HEADER FileHeader;//PE文件头

    IMAGE_OPTIONAL_HEADER32 OptionalHeader;//PE扩展头

} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

 

typedef struct _IMAGE_FILE_HEADER {

    WORD    Machine;  //014C-IMAGE_FILE_MACHINE_I386

    WORD    NumberOfSections; //PE节数量-0007个节

    DWORD   TimeDateStamp;   //时间戳E72B4FA9

    DWORD   PointerToSymbolTable;  //指向符号表0000

    DWORD   NumberOfSymbols;  //符号表数量0000

    WORD    SizeOfOptionalHeader; //扩展PE头大小00E0

    WORD Characteristics; //文件属性

} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 

typedef struct _IMAGE_OPTIONAL_HEADER {

    //

    // Standard fields.

    //

 

    WORD    Magic;   //010B-IMAGE_NT_OPTIONAL_HDR32_MAGIC

    BYTE    MajorLinkerVersion;   //0A-连接器主版本号

    BYTE    MinorLinkerVersion;  //00-连接器小版本号

    DWORD   SizeOfCode; //0000008A(138)-代码节大小

    DWORD   SizeOfInitializedData;   //0000004C(76)-已初始化数据大小

    DWORD   SizeOfUninitializedData;  //00000000(0)-未初始化数据大小

    DWORD   AddressOfEntryPoint;  //000110AA程序入口地址

    DWORD   BaseOfCode;  //00001000程序段基地址

    DWORD   BaseOfData;  //00001000数据段基地址 

 

    //

    // NT additional fields.

    //

 

    DWORD   ImageBase;  //镜像加载基地址00400000

    DWORD   SectionAlignment; //节对其0001000(4096)

    DWORD   FileAlignment;   //文件对齐0000200(512)

    WORD    MajorOperatingSystemVersion;  //操作系统主版本号0005

    WORD    MinorOperatingSystemVersion;  //操作系统小版本号0001

    WORD    MajorImageVersion;  //镜像主版本号0000

    WORD    MinorImageVersion;  //镜像小版本号0000

    WORD    MajorSubsystemVersion;   //子系统主版本号0005

    WORD    MinorSubsystemVersion;   //子系统小版本号0001

    DWORD   Win32VersionValue;   //0

    DWORD   SizeOfImage;  //镜像大小00022000

    DWORD   SizeOfHeaders;    //头大小0400

    DWORD   CheckSum;  //0

    WORD    Subsystem;  //03-IMAGE_SUBSYSTEM_WINDOWS_CUI

    WORD    DllCharacteristics;  

    DWORD   SizeOfStackReserve; //栈初始化大小010000

    DWORD   SizeOfStackCommit; //栈提交大小01000

    DWORD   SizeOfHeapReserve;   //堆初始化大小010000

    DWORD   SizeOfHeapCommit;  //堆提交大小01000

    DWORD   LoaderFlags;   //0

    DWORD   NumberOfRvaAndSizes;   //10(16)

    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表

} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

PE头,最开始的值是一个PE文件特有的签名,即“PE\0\0”,一旦操作系统在执行PE的时候,发现这个位置如果不是这个值,就会报错。然后,PE头分为2个组成部分:File HeaderOptional Header。其中在File Header里,包含有如下信息:

l         WORD Machine;//该文件运行所需要的CPU,对于Intel平台是14Ch

l         WORD NumberOfSections;//文件的节数目

l         DWORD TimeDateStamp;//文件创建日期和时间

l         DWORD PointerToSymbolTable;//用于调试

l         DWORD NumberOfSymbols;//符号表中符号个数

l         WORD SizeOfOptionalHeader;//OptionalHeader 结构大小

l         WORD Characteristics;//文件信息标记,区分文件是exe还是dll

    Optional Header里,包含了如下信息:

 

WORD Magic;//标志字(总是010bh)

BYTE MajorLinkerVersion;//连接器高版本号

BYTE MinorLinkerVersion;//连接器低版本号

DWORD SizeOfCode;//代码段大小
DWORD SizeOfInitializedData;//
已初始化数据块大小
DWORD SizeOfUninitializedData;//
未初始化数据块大小
DWORD AddressOfEntryPoint;//PE
装载器准备运行的PE文件的第一个指令的RVA
//
若要改变整个执行的流程,可以将该值指定到新的RVA,这样新RVA处的指令首先

//被执行。
DWORD BaseOfCode;//
代码段起始RVA
DWORD BaseOfData;//
数据段起始RVA
DWORD ImageBase;//PE
文件的装载地址
DWORD SectionAlignment;//
块对齐因子
DWORD FileAlignment;//
文件块对齐因子
WORD MajorOperatingSystemVersion;//
所需操作系统高位版本号
WORD MinorOperatingSystemVersion;//
所需操作系统低位版本号
WORD MajorImageVersion;//
用户自定义高位版本号
WORD MinorImageVersion;//
用户自定义低位版本号
WORD MajorSubsystemVersion;//win32
子系统版本。若PE文件是专门为Win32设计的
WORD MinorSubsystemVersion;//
该子系统版本必定是4.0否则对话框不会有3维立体感
DWORD Win32VersionValue;//
保留值,系统没用到的,一般被作为是否感染的标志
DWORD SizeOfImage;//
内存中整个PE映像体的尺寸
DWORD SizeOfHeaders;//
所有头+节表的大小
DWORD CheckSum;//
校验和
WORD Subsystem;//NT
用来识别PE文件属于哪个子系统
WORD DllCharacteristics;//
用来表示一个DLL映像是否为进程和线程的初始化及终止包含入口点的标记
DWORD SizeOfStackReserve;//
DWORD SizeOfStackCommit;//
DWORD SizeOfHeapReserve;//
DWORD SizeOfHeapCommit;//
//
堆栈大小 这些域控制要保留的地址空间数量,并且负责栈和默认堆的申请。在默认//情况下,栈和堆都拥有1个页面的申请值以及16个页面的保留值
DWORD LoaderFlags;//
告知装载器是否在装载时中止和调试,或者默认地正常运行
DWORD NumberOfRvaAndSizes;//
该字段标识了接下来的DataDirectory数组个数。
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
//IMAGE_DATA_DIRECTORY
结构数组。每个结构给出一个重要数据结构的RVA,比如//引入地址表等

 

2 PE文件格式全览

Optional Header里,有一个重要的数组即DataDirectory数组。在这个数组里面,保存着很多重要的表,比如导入表(IMP),导出表(EMP),IAT表等。

导入表记录了PE使用了多少库函数。导入表的结构如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk;//
指向一个 IMAGE_THUNK_DATA 结构数组的RVA
    }
    DWORD TimeDateStamp;//
文件生成的时间
    DWORD ForwarderChain;//
这个数据一般为0,可以不关心
    DWORD Name1;  // RVA
,指向DLL名字的指针,ASCII字符串
    DWORD FirstThunk; //
指向一个 IMAGE_THUNK_DATA 结构数组的RVA,这个数据与IAT所指向的地址一致

}IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR

    IMAGE_THUNK_DATA 这是一个DWORD类型的集合。通常我们将其解释为指向一个 IMAGE_IMPORT_BY_NAME 结构的指针,其定义如下:

IMAGE_THUNK_DATA{
    union {
        PBYTE ForwarderString;
        PDWORD Function;
        DWORD Ordinal;//
判定当前结构数据是不是以序号为输出的,如果是的话该值为

        //0x800000000,此时PIMAGE_IMPORT_BY_NAME不可做为名称使用
        PIMAGE_IMPORT_BY_NAME AddressOfData;
    }u1;
} IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;

typedef struct _IMAGE_IMPORT_BY_NAME{
    WORD Hint;//
函数输出序号
    BYTE Name1[1];//
输出函数名称

} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME


    节表头结构:

typedef struct _IMAGE_SECTION_HEADER {

BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节表名称,如“.text

//IMAGE_SIZEOF_SHORT_NAME=8

union {

DWORD PhysicalAddress;//物理地址

DWORD VirtualSize;//真实长度,这两个值是一个联合结构,可以使用其中的任何一个,

//一般是节的数据大小

} Misc;

DWORD VirtualAddress;//RVA

DWORD SizeOfRawData;//物理长度

DWORD PointerToRawData;//节基于文件的偏移量

DWORD PointerToRelocations;//重定位的偏移

DWORD PointerToLinenumbers;//行号表的偏移

WORD NumberOfRelocations;//重定位项数目

WORD NumberOfLinenumbers;//行号表的数目

DWORD Characteristics;//节属性 如可读,可写,可执行等

} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

1.概述(general

-------

所有的节在载入内存后都按“SectionAlignment”(节对齐)对齐,在文件中则以“FileAlignment”(文件对齐)对齐。节由节头中的相关项来描述:在文件中你可通过“PointerToRawData”(原始数据指针)来找到,在内存中你可通过“VirtualAddress”(虚拟地址)来找到;长度由“SizeOfRawData”(原始数据长度)决定。

 

根据节中包含的内容,可分为好几种节。大多数(并非所有)情况下,节中至少由一个数据目录,并在可选头的数据目录数组中有一个指针指向它。

 

 

2.代码节(code section

------------------------

首先,我将提到代码节。此节,至少,要将“IMAGE_SCN_CNT_CODE”(含有代码节)、“IMAGE_SCN_MEM_EXECUTE”(内存可执行节)和“IMAGE_SCN_MEM_READ”(内存可读节)等标志位设为1,并且“AddressOfEntryPoint”(入口点地址)将指向节中的某个地方,指向开发者希望首先执行的那个函数的开始处。

BaseOfCode”(代码基址)通常指向这一节的开始处,但是,如果一些非代码字节被放在代码之前的话,它也可能指向节中靠后的某个地方。

通常,除了可执行代码外,本节没有别的东东,并且通常只有一个代码节,但是不要太迷信这一点。

典型的节名有“.text”、“.code”、“AUTO”之类。

 

 

3.数据节(data section

------------------------

我们要讨论的下一件事情就是已初始化变量;本节包含的是已初始化的静态变量(象“static int i = 5;”)。它将,至少,使“IMAGE_SCN_CNT_INITIALIZED_DATA”(含有已初始化数据节)、“IMAGE_SCN_MEM_READ”(内存可读节)和“IMAGE_SCN_MEM_WRITE”(内存可写节)等标志位被置为1

一些链接器可能会将常量放在没有可写标志位的它们自己的节中。如果有一部分数据可共享,或者有其它的特定情况,那么可能会有更多的节,且它们的合适的标志位会被设置。

不管是一节,还是多节,它们都将处于从“BaseOfData”(数据基址)到“BaseOfData+SizeOfInitializedData”(数据基址+已初始化数据的大小)的范围之内。

典型的名称有“.data”、“.idata”、“DATA”、等等。

 

 

4.BSS节(bss section

----------------------

其后就是未初始化的数据(一些象“static int k;”之类的静态变量);本节十分象已初始化的数据,但它的“PointerToRawData”(文件偏移量)却为0,表明它的内容不存储在文件中;并且“IMAGE_SCN_CNT_UNINITIALIZED_DATA”(含有未初始化数据节)而不是“IMAGE_SCN_CNT_INITIALIZED_DATA”(含有已初始化数据节)标志位被置为1,表明在载入时它的内容应该被置为0。这就意味着,在文件中只有节头,没有节身;节身将由加载器创建,并全部为0字节。

它的长度由“SizeOfUninitializedData”(未初始化数据大小)确定。

典型的名称有“.bss”、“BSS”之类。

 

有些节数据“没有”被数据目录指向。它们的内容和结构是由编译器而不是链接器提供。

(栈段和堆段不是二进制文件中的节,它们是由加载器根据可选头中的栈大小和堆大小项来创建的。)

ELF文件格式

ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。

ELF文件格式提供了两种视图:连接视图和运行视图。在两种视图中,ELF头部(ELF Header)都位于文件的开始部分,位置固定,保存了路线图(road map),描述了该文件的组织情况。

3 ELF文件格式

   

在链接视图中,程序头部表(program header table)为可选。从程序的执行来看文件格式,程序头部表告诉系统如何来创建一个进程的内存映象。被用来建立进程映象(执行一个程序)的文件必须要有一个程序头部表,可重定位文件不需要这个头部表。

 

节区(section)保存着目标文件的信息,从链接视图看,包括指令,数据,符号表和重定位信息等等。从执行视图看,一个段通常包含几个节区,同样保存着指令,数据,符号表和重定位等信息。

 

节区头部表(section header table)包含了描述节区的信息。每个节区在这个表中有一个入口,该入口给出了节区的名字,大小等等信息。链接过程中的文件必须有一个节区头部表,而在执行视图中这个节区头部表为可选。

 

目标文件格式支持8位字节/32位体系结构。不过这种格式是可以扩展的,因此,目标文件以某些机器独立的格式来表达某些控制数据,使得能够以一种的公共的方式来识别和解释其内容。目标文件中的其它数据使用目标处理器的编码结构,而不管文件在何种机器上创建。



看文字不过瘾?点击我,进入腾讯课堂视频教学
麦洛科菲长期致力于IT安全技术的推广与普及,我们更专业!我们的学员已经广泛就职于BAT360等各大IT互联网公司。详情请参考我们的 业界反馈 《周哥教IT.C语言深学活用》视频

我们的微信公众号,敬请关注