PE头解析
PE 格式是Windows系统下组织可执行文件的格式。PE文件由文件头和对应的数据组成。目标是在不同的架构下装载器和编程工具不用重写。
PE中一大特点是不连续的位置大部分记录的都是相对地址(RVA),相对的是PE文件中记录的基地址(image base)的偏移量。进程是程序的执行状态的实体,每个进程都有自己独立的内存空间(编址)PE和内核等一起编制,所以image base也不总是确定的。
结构(参考:加密与解密)
数据结构
IMAGE_DOS_HEADER
参考:参考:http://www.openrce.org/reference_library/files/reference/PE%20Format.pdf
IMAGE_DOS_HEADER STRUCT { +0h WORD e_magic //Magic DOS signature MZ(4Dh 5Ah) DOS可执行文件标记 +2h WORD e_cblp //Bytes on last page of file +4h WORD e_cp //Pages in file +6h WORD e_crlc //Relocations +8h WORD e_cparhdr //Size of header in paragraphs +0ah WORD e_minalloc //Minimun extra paragraphs needs +0ch WORD e_maxalloc //Maximun extra paragraphs needs +0eh WORD e_ss //intial(relative)SS value DOS代码的初始化堆栈SS +10h WORD e_sp //intial SP value DOS代码的初始化堆栈指针SP +12h WORD e_csum //Checksum +14h WORD e_ip // intial IP value DOS代码的初始化指令入口[指针IP] +16h WORD e_cs //intial(relative)CS value DOS代码的初始堆栈入口 +18h WORD e_lfarlc //File Address of relocation table +1ah WORD e_ovno // Overlay number +1ch WORD e_res[4] //Reserved words +24h WORD e_oemid // OEM identifier(for e_oeminfo) +26h WORD e_oeminfo // OEM information;e_oemid specific +29h WORD e_res2[10] // Reserved words +3ch DWORD e_lfanew //Offset to start of PE header PE头相对于文件的偏移地址 } IMAGE_DOS_HEADER ENDS
对于PE来说DOS头是为了兼容16位程序的,现在都是32、64位所以我们只关心这个结构体中的两个成员(16位系统中PE头和内容是冗余数据)
e_magic、e_lfanew(第一个和最后一个)
- e_magic 是个标志MZ 0x4D5A 判断是否是PE文件用(不是唯一)
- e_lfanew PE头相对于文件的偏移地址
上图3CH是e_lfanew内容指向PE头地址内容位0000000E(小端存储)。
我们可以看到DOS头和PE头是间隔的那块区域叫DOS stub这个是存储16位程序的,对于32、64位系统无用
PNTHeader = ImageBase + (dosHeader->e_lfanew)
IMAGE_NT_HEADERS
typedef struct _IMAGE_NT_HEADERS { +0 hDWORD Signature // +4h IMAGE_FILE_HEADER FileHeader // +18h IMAGE_OPTIONAL_HEADER32 OptionalHeader // } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; #define IMAGE_NT_SIGNATURE 0x00004550
- Signature固定为0x00004550
IMAGE_FILE_HEADER
typedef struct _IMAGE_FILE_HEADER { +04h WORD Machine;//04h相对于_IMAGE_NT_HEADERS的,运行平台 +06h WORD NumberOfSections;//文件的区块数(*重要) +08h DWORD TimeDateStamp;//文件创建时间 和unix时间戳一样int(secound(now-19700101)) +0cH DWORD PointerToSymbolTable;//指向符号表(主要用于调试) +10H DWORD NumberOfSymbols;//符号表中符号个数(同上) +14H WORD SizeOfOptionalHeader;//IMAGE_OPTIONAL_HEADER32 结构大小(*重要)IMAGE_OPTIONAL_HEADER是长度可变的。 +16H WORD Characteristics;//文件属性多,种属性通过 “或运算” 同时拥有 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
IMAGE_OPTIONAL_HEADER(*重要)
由IMAGE_FILE_HEADER的SizeOfOptionalHeader决定大小(可变长) 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. // +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;
ImageBase文件在内存中载入地址,如果有文件占据这个位置装载器会进行应用基址重定位。 对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间一般不会被别的文件抢占。 对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被别的DLL使用,所以DLL文件中必须包含重定位信息,对应
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file. IMAGE_FILE_HEADER ->Characteristics(可以看到下面的数字位数不同,而且都是1、2、4、8他们总共加起来就是16看看二进制就知道了他们占的不是同一位与一下就能取到相对应位,linux的文件属性1、2、4也是一样的) EXE文件的默认优先装入地址被定为00400000h,而DLL文件的默认优先装入地址被定为10000000h
AddressOfEntryPoint字段 : 程序执行入口RVA,imageBase+AddressOfEntryPoint就是程序运行的时候首先执行代码处。一般指向.text节。
SectionAlignment:程序载入内存后区块(节)对齐大小,每个节被载入内存后必须和CPU内存页对齐(方便设置内存页属性)最小1Kh(4KB)
FileAlignment:PE文件在磁盘上的对齐大小,最小为200h(512byte)一个扇区大小。
关于对齐可以用winhex打开磁盘上的notpad.exe和内存中的notpad.exe,notpad装载的时候就被拉伸了。
IMAGE_DATA_DIRECTORY
这个是个结构存储的对应表位置和大小,至于这些表有什么,做什么需要下面详细解释。
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; //表首地址的RVA DWORD Size; //表长度 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
下面是微软的文档上截取的,其中我们比较关心的是导入表、重定位表(DLL等)
Size Field Description 8 Export Table The export table address and size. For more information see .edata Section (Image Only). 8 Import Table The import table address and size. For more information, see The .idata Section. 8 Resource Table The resource table address and size. For more information, see The .rsrc Section. 8 Exception Table The exception table address and size. For more information, see The .pdata Section. 8 Certificate Table The attribute certificate table address and size. For more information, see The Attribute Certificate Table (Image Only). 8 Base Relocation Table The base relocation table address and size. For more information, see The .reloc Section (Image Only). 8 Debug The debug data starting address and size. For more information, see The .debug Section. 8 Architecture Reserved, must be 0 8 Global Ptr The RVA of the value to be stored in the global pointer register. The size member of this structure must be set to zero. 8 TLS Table The thread local storage (TLS) table address and size. For more information, The .tls Section. 8 Load Config Table The load configuration table address and size. For more information, The Load Configuration Structure (Image Only). 8 Bound Import The bound import table address and size. 8 IAT The import address table address and size. For more information, see Import Address Table. 8 Delay Import Descriptor The delay import descriptor address and size. For more information, see Delay-Load Import Tables (Image Only). 8 CLR Runtime Header The CLR runtime header address and size. For more information, see The .cormeta Section (Object Only). 8 Reserved, must be zero
区块
区块由区块表映射,区块表紧跟IMAGE_NT_HEADERS;多少个区块表由_IMAGE_NT_HEADERS.FileHeader.NumberOfSections指定。
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //8字节的name 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; #define IMAGE_SIZEOF_SHORT_NAME 8
Name八个字节的不一定以“\0”借位的字符串表示节的名字
Misc、PhysicalAddress、VirtualSize叫什么都行是未对齐前节的大小(可以不准确,改了也没事,编译器生成的)
节在内存中的偏移地址,RVA。
SizeOfRawData 节在文件中对齐的尺寸
PointerToRawData 节在磁盘文件中的便宜,如果需要自己装载PE(不用操作系统装载)这个值有很多用处。
Characteristics 节属性(代码/数据,可读/可写)
常用 #define IMAGE_SCN_CNT_CODE 0x00000020 // Section contains code. #define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 // Section contains initialized data. #define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 // Section contains uninitialized data. #define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // Section can be discarded. #define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 // Section is not cachable. #define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 // Section is not pageable. #define IMAGE_SCN_MEM_SHARED 0x10000000 // Section is shareable. #define IMAGE_SCN_MEM_EXECUTE 0x20000000 // Section is executable. #define IMAGE_SCN_MEM_READ 0x40000000 // Section is readable. #define IMAGE_SCN_MEM_WRITE 0x80000000 // Section is writeable.
关于区块的对齐
区块对齐分两部分,FileAlignment、SectionAlignment分别代表文件中和内存中的对齐,为了有效读取和设置属性规定,FileAlignment现在好多程序都和SectionAlignment一样大小了。SectionAlignment内存对齐值在x86上一般是内存页大小4kB(1000H)在x64当中是8KB(2000H)。
文件偏移与虚拟地址的转换
FileAlignment、SectionAlignment不同需要将磁盘中的PE文件拉伸。
FileOffset=VA-RVA-k
参考:加密与解密
https://docs.microsoft.com/zh-cn/windows/win32/debug/pe-format