其实自己写过32PE,和PE32+ looker,只不过当初写的时候是看着一张图来写的,这个比较详细,就转过来
标 题: PE文件结构解析
作 者: lcx4时 间: 2016-4-8
链 接: http://www.lcx4.com/?post=51
如图2-2所示,一个完整的PE(Portable Executable)文件由DOS头,PE文件头,块表,块和调试信息(调试版本有效)组成。
图2-2 PE文件格式
在DOS头部,以e_magic开头,它的值是固定的”0x5a4d”,即(MZ)开头。最开头的是部分是DOS部首,DOS部首由两部分组成:DOS的MZ文件标志和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 Header和Optional 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,比如//引入地址表等
在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”之类。
有些节数据“没有”被数据目录指向。它们的内容和结构是由编译器而不是链接器提供。
(栈段和堆段不是二进制文件中的节,它们是由加载器根据可选头中的栈大小和堆大小项来创建的。)