PE是Portable executable(可移植的可执行文件)的简写,在Windows系统中PE文件是一种重要的文件格式,其中COM、PIF、SCR、EXE、dll等都是PE文件。所以学习PE文件是了解操作系统工作方式的一种很好的方法。
学习PE,首先要对PE有个大体的轮廓,所以本人在网上找到了两张PE文件结构图。基于这两张图更进一步的了解PE文件结构:
从图中不难看出,DOS头和PE头是由一个个的结构体构成的。不同的结构体中保存着不同的信息,一个结构体又包含着另一个结构体。通过这两张图,我们能对PE有个大概的轮廓了。
今天主要是对PE有个大概的了解,所以只是将其中的一部分结构体列出。
首先是MS—DOS头:
typedef struct _IMAGE_DOS_HEADER{
WORD e_magic;
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
WORD e_lfanew;
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
第一个成员则e_magic是帮助我们确定这是否是MS—DOS系统下的可执行文件
其中最后一个成员e_lfanew能帮我们找到PE文件头。e_lfanew的偏移地址一般都是0x3C。这里用到的工具是010Editor
F0的地址即是PE文件头的开始地址:
PE文件头结构体:
typedef struct _IMAGE_NT_HEADERS{
DWORD Signature; // PE标志
IMAGE_FILE_HEADER FileHeader; //文件头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展头
}IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
其中的Signature是PE文件头的标识内容总是0x50450000,ASCII码也就是PE00.
IMAGE_FILE_HEADER结构体:
typedef struct _IMAGE_FILE_HEADER{
WORD Machine; //运行平台
WORD NumberOfSections; //区段数量
DWORD TimeDateStamp; //文件的创建时间
DWORD PointerToSymbolTable; //符号表指针
DWORD NumberOfSymbols; //符号的数量
WORD SizeOfOptionalHeader; //扩展头的大小
WORD Characteristics; //文件属性
}IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
其中的NumberOfSections和SizeOfOptionalHeader相对来说更需要关注,一个是区段的数量,一个是扩展头的大小。
IMAGE_OPTIONAL_HEADER结构体:
typedef struct _IMAGE_OPTIONAL_HEADER
{
//
// Standard fields.
//
+18h WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
+1Ah BYTE MajorLinkerVersion; // 链接程序的主版本号
+1Bh BYTE MinorLinkerVersion; // 链接程序的次版本号
+1Ch DWORD SizeOfCode; // 所有含代码的节的总大小
+20h DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小
+24h DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小
+28h DWORD AddressOfEntryPoint; // 程序执行入口RVA
+2Ch DWORD BaseOfCode; // 代码的区块的起始RVA
+30h DWORD BaseOfData; // 数据的区块的起始RVA
//
// NT additional fields. 以下是属于NT结构增加的领域。
//
+34h DWORD ImageBase; // 程序的首选装载地址
+38h DWORD SectionAlignment; // 内存中的区块的对齐大小
+3Ch DWORD FileAlignment; // 文件中的区块的对齐大小
+40h WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号
+42h WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号
+44h WORD MajorImageVersion; // 可运行于操作系统的主版本号
+46h WORD MinorImageVersion; // 可运行于操作系统的次版本号
+48h WORD MajorSubsystemVersion; // 要求最低子系统版本的主版本号
+4Ah WORD MinorSubsystemVersion; // 要求最低子系统版本的次版本号
+4Ch DWORD Win32VersionValue; // 莫须有字段,不被病毒利用的话一般为0
+50h DWORD SizeOfImage; // 映像装入内存后的总尺寸
+54h DWORD SizeOfHeaders; // 所有头 + 区块表的尺寸大小
+58h DWORD CheckSum; // 映像的校检和
+5Ch WORD Subsystem; // 可执行文件期望的子系统
+5Eh WORD DllCharacteristics; // DllMain()函数何时被调用,默认为 0
+60h DWORD SizeOfStackReserve; // 初始化时的栈大小
+64h DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小
+68h DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
+6Ch DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小
+70h DWORD LoaderFlags; // 与调试有关,默认为 0
+74h DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT 发布以来 一直是16
+78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录表需要重点关注的
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
其中的AddressOfEntryPoint和ImageBase是更需要关注的 一个是程序执行入口的RVA,一个是程序的首选装载基址。
_IMAGE_SECTION_HEADER 区块结构体typedef struct _IMAGE_SECTION_HEADER{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union{
DWORD PhysicalAddress;
DWORD VirtualSize;
}Misc;
DWORD VirtualAddress;
DWORD SizeOfRavData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
其中的VirtualAddress、SizeOfRavData、PointerToRelocations和Characteristics更需要关注。
区段映射到内存地址将会改变。这就是区段对齐,也是遵循页对齐机制使得区段在内存中地址发生改变。
虚拟内存地址 = 装载基址 + 相对虚拟地址(RVA)
这些结构体是其中的一部分结构体,其余的结构体均在 IMAGE_OPTIONAL_HEADER结构体 的资源目录表中。资源目录表中的结构体也将是第二篇笔记要记录的内容了。