本章参考书《逆向工程核心原理》
什么是PE?
PE(Protable Executable),是Windows操作系统下使用的可执行文件,因为Windows分32位操作系统和64位操作系统,因此与之相对应有PE(或称为PE32),PE+(或称为PE32+)。这里主要分析PE32。
PE的分类
其中我们遇见的比较多的是exe可执行文件、sys驱动程序文件、dll动态链接库文件以及obj对象文件。
在这些文件中,除了obj对象文件,其他的文件都能够被直接或者间接地执行(例如驱动程序和库文件,就要使用相应的调试器,加载器或者是服务等来执行)。
PE文件的基本结构
-
- DOS头
DOS头的主要作用是使PE文件对DOS文件具有兼容性,表现为IMAGE_DOS_HEADER结构体(大小为64字节)。
1 typedef struct _IMAGE_DOS_HEADER{
2 WORD e_magic ;
3 WORD e_cblp ;
4 WORD e_cp ;
5 WORD e_crlc ;
6 WORD e_cparhdr ;
7 WORD e_minalloc ;
8 WORD e_maxalloc ;
9 WORD e_ss ;
10 WORD e_sp ;
11 WORD e_csum ;
12 WORD e_ip ;
13 WORD e_cs ;
14 WORD e_lfarlc ;
15 WORD e_ovno ;
16 WORD e_res[4] ;
17 WORD e_oemid ;
18 WORD e_oeminfo ;
19 WORD e_res2[10] ;
20 WORD e_lfanew ;
21 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
其中比较重要的是 e_magic (DOS签名) , e_lfanew(NT头的偏移)
其中“5A4D”(橘色标记)为e_magic(一般的PE文件的DOS头都是“5A4D”),而000000F8(红色标记)则是e_lfanew,NT头开始的位置,注意,此处为小端序表记法。
-
- DOS存根
DOS存根在DOS头的下方,是个可选项,大小不固定。DOS存根由代码和数据混合组成。
深色区域即为DOS存根,而用红框框出来的则是DOS系统下16位汇编指令,因此32位Windows不会识别,此16位汇编指令为:
1 0E PUSH CS
2 1F POP DS
3 BA0E00 MOV DX,000E ; DX = 0E : "This program cannot be run in DOS mode"
4 B409 MOV AH,09 ;WriteString()
5 CD21 INT 21
6 B8014C MOV AX,4C01 ;Exit()
7 CD21 INT 21
-
- NT头
NT头是PE头中的核心部分,在整个PE头中也占据很大的空间。
1 typedef struct _IMAGE_NT_HEADERS{
2 DWORD Signature ;
3 IMAGE_FILE_HEADER FileHeader ;
4 IMAGE_OPTIONAL_HEADER32 OptionalHeader;
5 }IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
IMAGE_NT_HEADERS结构体主要由以上3个成员组成。
根据DOS头中的e_lfanew值(000000F8),我们找到了NT头中的第一个变量Signture(50450000h)一般ASCLL码都是"PE"
-
-
- NT头:文件头(IMAGE_FILE_HEADER)
-
1 typedef struct _IMAGE_FILE_HEADER{
2 WORD Machine ;
3 WORD NumberOfSections ;
4 DWORD TimeDateStamp ;
5 DWORD PointerToSymbolTable ;
6 DWORD NumberOfSymbols ;
7 WORD SizeOfOptionalHeader ;
8 WORD Characteristics ;
9 }IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
#1.Machine
每个cpu都有唯一的Machine码。兼容32位Intel的Machine码为0x014c。
1 #define IMAGE_FILE_MACHINE_UNKNOWN 0
2 #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
3 #define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
4 #define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
5 #define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
6 #define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
7 #define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
8 #define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
9 #define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
10 #define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
11 #define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
12 #define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
13 #define IMAGE_FILE_MACHINE_THUMB 0x01c2
14 #define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
15 #define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
16 #define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
17 #define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS
18 #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
19 #define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#2.NumberOfSections
NumberOfSections用来指出此文件中存在的节区个数。
#3.SizeOfOptionalHeader
用来指出IMAGE_OPTIONAL_HEADER32的长度。尽管说,IMAGE_OPTIONAL_HEADER32结构体大小已经确定,但是为了和PE32+区分(在PE32+中,可选头为IMAGE_OPTIONAL_HEADER64,大小与PE中的可选头不同),因此需要SizeOfOptionalHeader来明确结构体大小。
#4.Characteristics
用来标识文件属性,来表示文件是否可运行,是否为DLL等信息。
1 #define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file.
2 #define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved externel references).
3 #define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file.
4 #define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file.
5 #define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Agressively trim working set
6 #define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses
7 #define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
8 #define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine.
9 #define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file
10 #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file.
11 #define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file.
12 #define IMAGE_FILE_SYSTEM 0x1000 // System File.
13 #define IMAGE_FILE_DLL 0x2000 // File is a DLL.
14 #define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine
15 #define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
#5.TimeDateStamp
记录编译器创造此文件的时间。
以下是notepad.exe的IMAGE_FILE_HEADER结构体:
014C machine
0005 number of sections
144CAAC5 TimeDateStamp
00000000 PointerToSymbolTable
00000000 NumberOfSymbols
00E0 SiezOfOptionalHeader
0102 Characteristics:IMAGE_FILE_32BIT_MACHINEIMAGE_FILE_EXECUTABLE_IMAGE
-
-
- NT头:可选头(IMAGE_OPTIONAL_HEADER32)
-
1 typedef struct _IMAGE_DATA_DIRECTORY {
2 DWORD VirtualAddress;
3 DWORD Size;
4 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
5
6 #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
7
8 //
9 // Optional header format.
10 //
11
12 typedef struct _IMAGE_OPTIONAL_HEADER {
13 //
14 // Standard fields.
15 //
16
17 WORD Magic;
18 BYTE MajorLinkerVersion;
19 BYTE MinorLinkerVersion;
20 DWORD SizeOfCode;
21 DWORD SizeOfInitializedData;
22 DWORD SizeOfUninitializedData;
23 DWORD AddressOfEntryPoint;
24 DWORD BaseOfCode;
25 DWORD BaseOfData;
26
27 //
28 // NT additional fields.
29 //
30
31 DWORD ImageBase;
32 DWORD SectionAlignment;
33 DWORD FileAlignment;
34 WORD MajorOperatingSystemVersion;
35 WORD MinorOperatingSystemVersion;
36 WORD MajorImageVersion;
37 WORD MinorImageVersion;
38 WORD MajorSubsystemVersion;
39 WORD MinorSubsystemVersion;
40 DWORD Win32VersionValue;
41 DWORD SizeOfImage;
42 DWORD SizeOfHeaders;
43 DWORD CheckSum;
44 WORD Subsystem;
45 WORD DllCharacteristics;
46 DWORD SizeOfStackReserve;
47 DWORD SizeOfStackCommit;
48 DWORD SizeOfHeapReserve;
49 DWORD SizeOfHeapCommit;
50 DWORD LoaderFlags;
51 DWORD NumberOfRvaAndSizes;
52 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
53 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
其中红字标识的是可选头中比较重要的变量,以下我来对它们一一介绍。
#1.Magic
当文件为PE时,可选头为IMAGE_OPTIONAL_HEADER32,Magic = 10B
当文件为PE+时,可选头为IMAGE_OPTIONAL_HEADER64,Magic = 20B
#2.AddressOfEntryPoint
程序最先被执行的代码起始地址。相当重要。
#3.ImageBase
当PE文件被装载到内存空间中去时,ImageBase指出文件的优先装入地址。执行PE文件时,PE装载器先创建进程,再将文件载入内存,然后把EIP寄存器的值设置为ImageBase + AddressOfEntryPoint 。
#4.SectionAlignment,FileAlignment
SectionAlignment指定了节区在内存中的最小单位
FileAlignment指定了节区在磁盘中的最小单位
一个文件的SectionAlignment和FileAlignment可能相同,也有可能不同。
磁盘文件或内存中的节区大小必定为FileAlignment或SectionAlignment值的整数倍。
#5.SizeOfImage
SizeOfImage指定了PE在加载入内存中所占空间的大小,在一般情况下,文件的大小与加载到内存中的大小是不同的。
#6.SizeOfHeaders
整个PE头的大小,值为FileAlignment的整数倍。第一节区所在位置与SizeOfHeaders距文件偏移量相同。
#7.Subsystem
用来区分不同的文件。
#8.NumberOfRvaAndSizes
指定DataDirectory(IMAGE_OPTIONAL_HEADER32结构体中最后一个成员)数组的个数。
#9.DataDirectory
DataDirectory是由IMAGE_DATA_DIRECTORY结构体组成的数组,数组的每项都有被定义的值。
DataDirectory[0] = EXPORT Directory
DataDirectory[1] = IMPORT Directory
DataDirectory[2] = RESOURCE Directory
DataDirectory[3] = EXCEPTION Directory
DataDirectory[4] = SECURITY Directory
DataDirectory[5] = BASERELOC Directory
DataDirectory[6] = DEBUG Directory
DataDirectory[7] = COPYRIGHT Directory
DataDirectory[8] = GLOBALPTR Directory
DataDirectory[9] = TLS Directory
DataDirectory[A] = LOAD_CONFIG Directory
DataDirectory[B] = BOUND_IMPORT Directory
DataDirectory[C] = IAT Directory
DataDirectory[D] = DELAY_IMPORT Directory
DataDirectory[E] = COM_DESCRIPTOR Directory
DataDirectory[F] = Reserved Directory
介绍完了可选头里的每个成员,再带大家实际分析一下notepad.exe
地址 十六进制 备注
000110 010B magic
000120 0001B2B0 AdressOfEntryPoint
000130 00001000 SectionAlignment
000134 00000200 FileAlignment
00014C 00000400 SizeOfHeaders
000154 0002 Subsystem
000170 00000000 DataDirectory[0].VirtualAdress(RVA of EXPORT Directory)
000178 0001F4AC DataDirectory[1].VirtualAdress(RVA of IMPORT Directory)
00017C 00000230 DataDirectory[1].Size(size of IMPORT Directory)
这里只列出来了一些比较重要的成员,各位朋友们可以自己练习,把剩下的成员都标识出来哦!~
至此,NT头已经讲完了,在进入下一内容前,我建议各位读者把NT头再回顾一下。
-
- 节区头
节区头由IMAGE_SECTION_HEADER结构体组成的数组,有多少个节区就有多少个结构体。并且在节区中有这样的规定:把代码(code),数据(data)和资源(resourse)分类存储在不同的节区里。
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER{BYTE Name[IMAGE_SIZEOF_SHORT_NAME];union{DWORD PhysicalAddress;DWORD VirtualSize; } Misc;DWORD VirtualAddress;DWORD SizeOfRawData;DWORD PointerToRawData;DWORD PointerToRelocations;DWORD PointerToLinenumbers;WORD NumberOfRelocations;WORD NumberOfLinenumbers;DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
VirtualSize :内存中节区所占大小。
VirtualAddress :内存中节区起始地址(RVA)。
SizeOfRawData:磁盘文件中节区所占大小。
PointerToRawData:磁盘文件中节区起始位置。
characteristics:节区属性。
1 //
2 // Section characteristics.
3 //
4 // IMAGE_SCN_TYPE_REG 0x00000000 // Reserved.
5 // IMAGE_SCN_TYPE_DSECT 0x00000001 // Reserved.
6 // IMAGE_SCN_TYPE_NOLOAD 0x00000002 // Reserved.
7 // IMAGE_SCN_TYPE_GROUP 0x00000004 // Reserved.
8 #define IMAGE_SCN_TYPE_NO_PAD 0x00000008 // Reserved.
9 // IMAGE_SCN_TYPE_COPY 0x00000010 // Reserved.
10
11 #define IMAGE_SCN_CNT_CODE 0x00000020 // Section contains code.
12 #define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 // Section contains initialized data.
13 #define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 // Section contains uninitialized data.
14
15 #define IMAGE_SCN_LNK_OTHER 0x00000100 // Reserved.
16 #define IMAGE_SCN_LNK_INFO 0x00000200 // Section contains comments or some other type of information.
17 // IMAGE_SCN_TYPE_OVER 0x00000400 // Reserved.
18 #define IMAGE_SCN_LNK_REMOVE 0x00000800 // Section contents will not become part of image.
19 #define IMAGE_SCN_LNK_COMDAT 0x00001000 // Section contents comdat.
20 // 0x00002000 // Reserved.
21 // IMAGE_SCN_MEM_PROTECTED - Obsolete 0x00004000
22 #define IMAGE_SCN_NO_DEFER_SPEC_EXC 0x00004000 // Reset speculative exceptions handling bits in the TLB entries for this section.
23 #define IMAGE_SCN_GPREL 0x00008000 // Section content can be accessed relative to GP
24 #define IMAGE_SCN_MEM_FARDATA 0x00008000
25 // IMAGE_SCN_MEM_SYSHEAP - Obsolete 0x00010000
26 #define IMAGE_SCN_MEM_PURGEABLE 0x00020000
27 #define IMAGE_SCN_MEM_16BIT 0x00020000
28 #define IMAGE_SCN_MEM_LOCKED 0x00040000
29 #define IMAGE_SCN_MEM_PRELOAD 0x00080000
30
31 #define IMAGE_SCN_ALIGN_1BYTES 0x00100000 //
32 #define IMAGE_SCN_ALIGN_2BYTES 0x00200000 //
33 #define IMAGE_SCN_ALIGN_4BYTES 0x00300000 //
34 #define IMAGE_SCN_ALIGN_8BYTES 0x00400000 //
35 #define IMAGE_SCN_ALIGN_16BYTES 0x00500000 // Default alignment if no others are specified.
36 #define IMAGE_SCN_ALIGN_32BYTES 0x00600000 //
37 #define IMAGE_SCN_ALIGN_64BYTES 0x00700000 //
38 #define IMAGE_SCN_ALIGN_128BYTES 0x00800000 //
39 #define IMAGE_SCN_ALIGN_256BYTES 0x00900000 //
40 #define IMAGE_SCN_ALIGN_512BYTES 0x00A00000 //
41 #define IMAGE_SCN_ALIGN_1024BYTES 0x00B00000 //
42 #define IMAGE_SCN_ALIGN_2048BYTES 0x00C00000 //
43 #define IMAGE_SCN_ALIGN_4096BYTES 0x00D00000 //
44 #define IMAGE_SCN_ALIGN_8192BYTES 0x00E00000 //
45 // Unused 0x00F00000
46
47 #define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 // Section contains extended relocations.
48 #define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // Section can be discarded.
49 #define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 // Section is not cachable.
50 #define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 // Section is not pageable.
51 #define IMAGE_SCN_MEM_SHARED 0x10000000 // Section is shareable.
52 #define IMAGE_SCN_MEM_EXECUTE 0x20000000 // Section is executable.
53 #define IMAGE_SCN_MEM_READ 0x40000000 // Section is readable.
54 #define IMAGE_SCN_MEM_WRITE 0x80000000 // Section is writeable.
接下来以notepad.exe的节区为例做分析:
1 0001F0 2E746578 74000000 Name(.text)
2 0001F8 0001AE2C Misc.VirtualSize
3 0001FC 00001000 VirtualAddress
4 000200 0001B000 SizeOfRawData
5 000204 00000400 PointerToRawData
6 000214 60000020 Characteristics: IMAGE_SCN_MEM_EXECUTE
IMAGE_SCN_MEM_READ
IMAGE_SCN_CNT_CODE
由于.text是第一块节区,因此VirtualAddress和PointerToRawData是不带任何值的(是由IMAGE_OPTIONAL_HEADER32中)SectionAlignment和FileAlignment确定的。
因为文件从磁盘中载入到内存中,因为载入点不同,会引起地址的变化,在磁盘中的相对位置称为RAW,在内存中的相对地址是RVA,在内存中的绝对地址是VA。这三者的对应转换关系如下:
VA = ImageBase + RVA
RAW = RVA - VirtualAddress + PointerToRawData
比如说,现在有一段数据,RVA = 7000 ,查节区,发现第一节区为1B000(SizeOfRawData可知)> 7000 ,说明这个数据段在第一节区,再将7000减去1000(内存中节区的起始地址),加上400(文件中节区的起始地址),就得到该数据段在文件中的位置。
以上的地址转换称为:RVA to RAW,极为重要!!
好了,本篇文章的内容主要是讲解PE文件和其重要的PE头结构,以及重要的地址转换公式,在(二)中将继续讲解PE头的核心内容IAT(Import Address Table)。
欢迎读者讨论,批评与指正!