PE (Portable Executable) 文件是微软公司设计用于其所有 Win32 操作系统(Windows NT/9X/2000/XP)的可执行文件格式。很多时候需要判断文件是否为WIN32 PE格式,以便此文件可以在WIN32系统下正常运行,这在病毒和反病毒软件中尤为重要。与COM文件和EXE文件相比,PE文件格式比较复杂,限于篇幅,我们在这里只简单地作一些介绍,有兴趣的读者可查找有关参考书进行详细的研究。PE文件格式如图4-2所示。
图 4-2 PE文件格式
我们首先来看看PE 表头。和其它的微软可执行文件格式一样,PE 表头并非在文件的最开始处。所有PE文件必须以一个简单的DOS MZ头开始,紧接着DOS MZ头的是所谓的DOS stub,DOS stub是一个极小 DOS 程序(一般是几百个字节),用来输出像“该程序不能在DOS模式下运行”这样的信息。这也就是为什么在纯DOS方式下运行PE格式文件会给出一条错误提示信息的原因。当 Win32 加载器把一个 PE 文件映像到内存,内存映像文件的第一个字节对应到 DOS Stub 的第一个字节。
紧接着 DOS stub 的是PE头。PE头是PE相关结构 IMAGE_NT_HEADERS 的简称,其中包含了许多PE装载器用到的重要域。PE头中包含了PE标志,此标志为DWORD类型,其值为“PE\0\
PE文件的真正内容划分成块,称之为段。每段节是一块拥有共同属性的数据。PE头接下来的数据结构是段表,每个结构包含对应段的属性、文件偏移量、虚拟偏移量等内容。PE文件里有多少个段,此结构数组内就有多少个成员。
下面,我们分析PE文件的有关数据结构。
//NT头
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE文件头标志:“PE\0\
IMAGE_FILE_HEADER FileHeader; //PE文件物理分布的信息
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //PE文件逻辑分布的信息
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
//文件头
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //该文件运行所需要的CPU,对于Intel平台是0x
WORD NumberOfSections; // EXE的OBJ 中的段的个数
DWORD TimeDateStamp; //文件创建日期和时间
DWORD PointerToSymbolTable; // COFF 符号表格的偏移位置,此字段只对 COFF 出错信息有用,其中COFF是指Common Object File Format(公共目标文件格式)
DWORD NumberOfSymbols; //COFF符号表中符号个数
WORD SizeOfOptionalHeader; //OptionalHeader 结构大小
WORD Characteristics; //文件信息标记,区分文件是EXE还是DLL
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
//可选头
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //用来定义 image 的状态,其中0x0107表示是一个ROM image,0x010B表示是一个正常的(一般的)EXE image
BYTE MajorLinkerVersion; //连接器主版本号
BYTE MinorLinkerVersion; //连接器次版本号
DWORD SizeOfCode; //所有的代码段大小的总和
DWORD SizeOfInitializedData; //所有已初始化数据块大小的总和
DWORD SizeOfUninitializedData; //所有未初始化数据块大小的总和
DWORD AddressOfEntryPoint; //PE装载器准备运行的PE文件的第一个指令的RVA,若要改变整个执行的流程,可以将该值指定到新的RVA,这样新RVA处的指令首先被执行。其中RVA是指Relative Virtual Adress(相对虚拟地址)
DWORD BaseOfCode; //代码段起始RVA
DWORD BaseOfData; //数据段起始RVA
DWORD ImageBase; //PE文件的装载地址
DWORD SectionAlignment; //块对齐,一旦映像到内存中,每一个段的起始地址都是该值的倍数
DWORD FileAlignment; //文件块对齐,每个段的数据的起始地址都是该值的倍数
WORD MajorOperatingSystemVersion; //文件执行所需操作系统的主版本号
WORD MinorOperatingSystemVersion;// 文件执行所需操作系统的次版本号
WORD MajorImageVersion; //用户自定义的主版本号
WORD MinorImageVersion; //用户自定义的次版本号
WORD MajorSubsystemVersion; //win32子系统主版本。
WORD MinorSubsystemVersion; //win32子系统次版本。若PE文件是专门为Win32设计的,该子系统版本必定是4.0,否则对话框不会有三维立体感
DWORD Win32VersionValue; //保留,通常取0
DWORD SizeOfImage; //内存中整个PE映像体的大小
DWORD SizeOfHeaders; //所有表头和段表大小的总和
DWORD CheckSum; //校验和,通常取0
WORD Subsystem; //NT用来识别PE文件属于哪个子系统
WORD DllCharacteristics; //DLL特征值,通常取0
DWORD SizeOfStackReserve; //线程初始堆栈的保留大小
DWORD SizeOfStackCommit; //分配给线程初始堆栈的内存数量
DWORD SizeOfHeapReserve; //保留给最初的进程堆的虚拟内存数量
DWORD SizeOfHeapCommit; //分配给最初的进程堆的虚拟内存数量
DWORD LoaderFlags; // 装载标志
DWORD NumberOfRvaAndSizes; //在数据目录数组中的项目个数
IMAGE_DATA_DIRECTORY
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //=16
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
DOS病毒编制的关键技术-PE文件[2]
进入社区
//数据目录
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //表的RVA地址
DWORD Size; //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
//数据入口
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // 导出目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // 导入目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // 资源目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // 异常目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // 安全目录
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 //重定位基表
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // 调试目录
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 //描述字符串
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 //装载配置目录
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 //在表头中绑定的导入目录
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // 导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 //延迟装载导入描述符
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 //COM运行描述符
//段表
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //段表名,如“.text”等
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;
//资源目录
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics; //资源的特性
DWORD TimeDateStamp; //资源的产生时刻
WORD MajorVersion; //资源的主版本号,目前总是为0
WORD MinorVersion; //资源的次版本号,目前总是为0
WORD NumberOfNamedEntries; //使用名称的元素的个数
WORD NumberOfIdEntries; //使用ID的元素的个数
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
//资源目录入口
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset; //名字偏移地址,取值31
DWORD NameIsString; //名字是字符串,取1
};
DWORD Name; //名字
WORD Id; //ID号
};
union {
DWORD OffsetToData; //数据偏移地址
struct {
DWORD OffsetToDirectory; //目录偏移地址,取31
DWORD DataIsDirectory;
};
};
}IMAGE_RESOURCE_DIRECTORY_ENTRY,
*PIMAGE_RESOURCE_DIRECTORY_ENTRY;
//资源目录名
typedef struct _IMAGE_RESOURCE_DIRECTORY_STRING {
WORD Length; //目录名长度
CHAR NameString[ 1 ]; //目录名字串
}IMAGE_RESOURCE_DIRECTORY_STRING,
*PIMAGE_RESOURCE_DIRECTORY_STRING;
//资源目录Unicode名
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length; //目录名长度
WCHAR NameString[ 1 ]; //目录名字串
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
//资源数据入口
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; //数据偏移地址
DWORD Size; //大小
DWORD CodePage; //代码页
DWORD Reserved; //保留
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
装载一个PE文件的主要步骤如下:
(1)当PE文件被执行,PE装载器检查 DOS MZ头里的PE头偏移量。如果找到,则跳转到PE头。
(2)PE装载器检查PE头的有效性。如果有效,就跳转到PE头的尾部。
(3)紧跟PE头的是段表,PE装载器读取其中的段的信息,并采用文件映射方法将这些段映射到内存,同时附上段表里指定的段的属性。
(4)PE文件映射入内存后,PE装载器将处理PE文件中类似引入表逻辑部分。
此文章节选自《计算机病毒与木马程序剖析》