PE檔案格式學習(二):總體結構
1.概述
PE檔案分為幾個部分,分別是:
- DOS頭
- DOS Stub
- NT頭(PE頭)
- 檔案頭
- 可選頭
- 區段頭(一個數組,每個元素都是一個結構體,稱之為IMAGE_SECTION_HEADER)
- .text
- .rdata
- .data
- .rsrc
- .reloc
- ...
本篇文章主要圍繞整體結構做一個梳理,不會做太多細節的講解。(比如說可選頭裡的資料目錄表,它包含16種表,會在接下來的系列文章分別進行講解,熟悉了資料目錄表就可以說熟悉了PE)
2.DOS頭和DOS Stub
DOS頭和DOS Stub在現在的Windows系統上基本都是擺設(對於軟體安全還是有一定價值的,PE的變形、壓縮、植入都可能利用Stub部分,以後有機會我會寫一個系列專門講解PE檔案操作相關的應用技術),它存在的原因是為了向下相容、適配DOS系統,對於DOS頭和它的Stub部分就只要稍微瞭解一些主要結構體欄位就可以了。
PE檔案開頭的兩個位元組,在上篇文章介紹過,是字元“MZ”,也就是0x5A4D,它就是DOS頭的開端,從這裡開始的64個位元組大小都是DOS頭的範圍。
DOS頭的結構體是:
struct _IMAGE_DOS_HEADER{ 0X00 WORD magic; 0X02 WORD e_cblp; 0X04 WORD e_cp; 0X06 WORD e_crlc; 0X08 WORD e_cparhdr; 0X0A WORD e_minalloc; 0X0C WORD e_maxalloc; 0X0E WORD e_ss; 0X10 WORD e_sp; 0X12 WORD e_csum; 0X14 WORD e_ip; 0X16 WORD e_cs; 0X18 WORD e_lfarlc; 0X1A WORD e_ovno; 0x1C WORD e_res[4]; 0x24 WORD e_oemid; 0x26 WORD e_oeminfo; 0x28 WORD e_res2[10]; 0x3C DWORD e_lfanew; };
DOS頭的結構體欄位雖然很多,但是絕大部分現在都可以不用瞭解(除非你想做計算機歷史學家,^_^)。對於不重要的欄位就不解釋了,下面出現的結構體也只會摘出重要的欄位做出解釋。
magic:它幾乎永遠是5A4D,也就是字元“MZ”。
e_lfanew:指向PE頭的檔案偏移。下圖中e_lfanew的值是0x100,它也幾乎總是這個值。PE頭就在地址0x100處開始,它通常是0x00004550,也就是PE\0\0。
下圖顯示了一些資訊,其中0x3c處就是e_lfanew!在十六進位制工具中明明顯示的是00010000,也就是0x10000啊!怎麼會是0x100呢?這就需要了解一些小端排序的知識了。
儲存在Windows系統中的資料基本上都是小端排序,資料讀取的順序不再是從左往右,而是從高地址往低地址,一個位元組一個位元組的讀。上圖中的e_flanew從高地址到低地址依次是:00 00 01 00,因此它其實是0x100。小端排序是一種CPU遵循的標準或者說格式,為了使CPU能夠正確讀資料,系統中的大部分程式都被編譯成了小端排序(Java這樣的基於虛擬機器編寫的程式除外)。
DOS頭的大小是64個位元組也就是0x40,從0x40開始到e_flanew指向的0x100為止的部分就是DOS Stub了。
DOS Stub是DOS系統的可執行檔案,在DOS系統中會執行裡面的程式碼,只需要將一個包含完整DOS頭和DOS Stub的Win32程式放到DOS系統中執行就可以執行裡面的程式碼了。
DOS頭和DOS Stub就介紹到這裡吧。
3.NT頭
上面說道e_lfanew指向的檔案偏移是0x100,它指向的位置的值是0x00004550,也幾乎總是這個值,它轉成ASCII碼就是PE\0\0。這裡就是NT頭的開始。下面介紹一下NT頭的結構體。
struct _IMAGE_NT_HEADERS{
0x00 DWORD Signature;
0x04 _IMAGE_FILE_HEADER FileHeader;
0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader;
};
Signatue:也就是“PE\0\0”,佔4個位元組。
FileHeader:檔案頭,本身也是一個結構體,後面會說
OptionalHeader:可選頭,本身也是一個結構體,後面會說
3.1 檔案頭
檔案頭佔20個位元組,檔案頭也是一個結構體。
struct _IMAGE_FILE_HEADER{
0x00 WORD Machine;
0x02 WORD NumberOfSections;
0x04 DWORD TimeDateStamp;
0x08 DWORD PointerToSymbolTable;
0x0c DWORD NumberOfSymbols;
0x10 WORD SizeOfOptionalHeader;
0x12 WORD Characteristics;
};
Machine:目標平臺,對照下面的表
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.(常見)
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2
#define IMAGE_FILE_MACHINE_AM33 0x01d3
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1
#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#define IMAGE_FILE_MACHINE_CEF 0x0CEF
#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian
#define IMAGE_FILE_MACHINE_CEE 0xC0EE
NumberOfSections:區段表數量
SizeOfOptionalHeader:可選頭的大小(位元組數):32位預設E0H,64位預設F0H(可修改)
Characteristics:檔案型別,對照下面的表
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved externel references).(常見)
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM 0x1000 // System File.
#define IMAGE_FILE_DLL 0x2000 // File is a DLL.(常見)
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
3.2 可選頭
介紹完NT頭的Signature和檔案頭還有最後一個欄位:可選頭。雖然它叫可選頭,但是它非常的重要,叫“必選頭”才對!可選頭包含了非常多的資訊,它在32位系統中的大小是E0h(224個位元組),64位系統中的大小是F0h(240個位元組)。
它的結構體:
struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Magic:0x10B是32位PE,0x20B是64位PE,0x107是ROM映像
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b // 32位PE可選頭
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b // 64位PE可選頭
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC 0x107 // ROM映像
AddressOfEntryPoint://程式入口的RVA值,程式最先執行程式碼的地址
BaseOfImage:PE檔案的裝載地址
SizeOfImage:記憶體中整個PE映像體的尺寸
SectionAlignment:區段在記憶體中的對齊值,一般為0x1000
FileAlignment:區段在檔案中的對齊值,一般為0x200
SizeOfHeaders:所有頭+節表的大小,即整個PE頭的大小
DllCharacteristics:DLL檔案的屬性值,DLL檔案才會生效,參見下面的表
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 // DLL can move.
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 // Code Integrity Image
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 // Image is NX compatible
#define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200 // Image understands isolation and doesn't want it
#define IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400 // Image does not use SEH. No SE handler may reside in this image
#define IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 // Do not bind this image.
// 0x1000 // Reserved.
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 // Driver uses WDM model
// 0x4000 // Reserved.
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000
NumberOfRvaAndSizes:資料目錄成員的數量,一般為0x10
DataDirectory:資料目錄表陣列,每個結構給出一個重要資料結構的RVA和size,參見下面的表
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
資料目錄表的結構體是這樣的:
struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
資料目錄表的解析後面的文章會逐一介紹,它是PE檔案格式的重中之重。
4.區段頭和區段
區段頭是一個數組,裡面的每個元素都是一個結構體,這個結構體叫做IMAGE_SECTION_HEADER,每個結構體的大小是40個位元組,如下:
struct _IMAGE_SECTION_HEADER {
BYTE Name[8];
union {
DWORD PhysicalAddress;
WORD VirtualSize;
} Misc
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
Name:區段名
PhysicalAddress:實體地址,它的值一般跟VirtualSize相同,所以它有些不準確。
VirtualSize:真實大小,區段未做對齊處理之前的大小,跟PhysicalAddress同屬一個聯合體,可以使用任何一個,一般使用VirtualSize。
VirtualAddress:區段在記憶體中的RVA
SizeOfRawData:區段在磁碟中的大小
PointerToRawData:區段在檔案中的偏移
PointerToRelocations:在obj檔案中使用,指向重定位表的指標
PointerToLinenumbers:行號表的位置,供除錯使用,一般為0x0000
NumberOfRelocations:在obj檔案中使用,此區段重定位表項的數量
NumberOfLinenumbers:行號表中的行號數量
Characteristics:區段屬性,參見下表
IMAGE_SCN_CNT_CODE 0x00000020 該節區含程式碼
IMAGE_SCN_MEM_SHARED 0x10000000 該節區為可共享
IMAGE_SCN_MEM_EXECUTE 0x20000000 該節區為可執行
IMAGE_SCN_MEM_READ 0x40000000 該節區為可讀
IMAGE_SCN_MEM_WRITE 0x80000000 該節區為可寫
區段頭介紹完了,已經說明過區段頭是一個數組,檔案頭中有區段的數量,這個數量就是區段頭陣列的大小,緊接著區段頭後面的就是各種區段,常見的區段見下面的表:
.text: 預設的程式碼區塊,內容都是指令程式碼
.data:預設的讀寫資料塊,全域性變數、靜態變數一般放在這裡。
.rdata: 預設的只讀資料塊 一般很少用到。
.idata:包含外來的DLL資料及資料資訊,也就是輸入表 ,通常情況下把他合併到.rdata中。
.edata: 當建立一個用於輸出資料的可執行檔案時(輸出表),資料會放在這裡,通常情況下會被合併到.text 或.tdata中。
.rsrs:資源塊 包含一切圖示選單等。
5.總結
本文對PE的結構有了一個總體的介紹,到此應該對PE的結構有了一個整體性的瞭解。PE檔案看似簡單,實際上使用的時候靈活度很大,對PE檔案的變形壓縮等操作的前提是要對PE每一個欄位、每一處結構有充分的瞭解,所以PE檔案中重要的欄位要非常的熟練。
檔案結構中有一些欄位需要記憶,以加深對PE的印象。
比如說,有大量對屬性、型別的定義:
- DOS頭中的magic欄位:4D5A,"MZ"
- 檔案頭中的 Machine:目標平臺的型別
- 檔案頭中的Characteristics:檔案的型別
- 可選頭中的Magic: 檔案的型別,重點分別32位還是64位
- 可選頭中的 DllCharacteristics:只有DLL檔案才有效,定義DLL的一些屬性
- 區段頭中的Characteristics: 區段的屬性
還有各個結構中的重點欄位:
- DOS頭中的magic、e_lfanew
- 檔案頭中的machine、characteristics、numberOfSections、sizeOfOptionalHeader
- 可選頭中的magic、DllCharacteristics、addressOfEntry、imageOfBase、sizeOfHeaders、sizeOfImage
- 區段頭中的VirtualSize、VirtualAddress、PointerToRawData、sizeOfRawData、numberOfRelocations、pointerToRelocations、numberOfLinenumbers、pointerToLinenumbers、name、Characteristics
下面的圖示明瞭PE檔案各部分的範圍:
上圖中,區段頭有五個成員,每一個長度都是28h,意味著這個檔案有5個區段。
區段頭的後面5個區段依次排列。每個區段的起始點在區段頭的pointerToRawdata中記錄,長度在sizeOfRawData中記錄。
再後面的內容儲存了很多表的內容以及雜項。這些表都在可選頭的資料目錄表裡記錄,起始地址就儲存在資料目錄表裡,下篇文章開始介紹資料目錄表裡的內容。