本次的了解主要講解 PE的基本概念、MS-DOS文件頭、PE文件頭、區塊、輸入表、輸出表等。
這里我將會結合一個簡單的小程序來加深我對PE文件結構的了解。
?
使用學習工具:有StudyPE、LordPE、PEID。
?學習PE建議看書。。和自己動手。。。
?
PE文件:
在WIN上,32位的可執行文件是PE文件,64位的是PE32+文件 ,DLL文件的格式和PE格式差不多,唯一的區別是PE和DLL的有一個字段標識這個文件是EXE還是DLL。
如上就是一個PE文件的結構圖,PE文件使用的是一個平面地址空間,所有的數據都融合在一起,文件的內容又被分割為不同的區塊(Section),
各個區塊按頁的邊界來對齊。每個塊都有自己的屬性(是否可讀,是否可寫,是否可執行等等)。
?
基地址:
? ? ? 當PE文件被裝載器裝載了之后,內存中的板塊被稱為模塊。映射文件的起始地址被稱為模塊句柄---內存中的模塊代表這進程從這個可執行文件中所需要的代碼、數據、資源、輸入表、輸出表及其他東西所使用的東西放在一個連續的內存塊中。在裝載中,PE文件的一個字段會告訴系統把文件映射到內存需要多少內存,不能被映射的數據被放置在文件的尾部。
? ? ? 在WIN32中,可以使用HMODULE GetModuleHandle(LPCTSTR lpModuleName)來獲得一個模塊的名稱。當傳遞一個可執行文件或者DLL作為參數,
如果系統成功找到這個文件,就會返回該可執行文件或者DLL文件映像加載到的基地址。
? ? ? 在PE文件中,有一個字符設置了基地址,VC++建立的exe文件的基地址是0x00400000h,DLL文件的基地址是0x10000000h。
?
相對虛擬地址:
? ? 為了讓程序的載入更加的靈活-也為了在PE文件中出現有確定的內存地址,出現了相對虛擬地址(Relative Vritual Address, RVA),當你的程序加載后,假設你的text塊的RVA = 0x00001000h,映射到程序中時,VA(虛擬地址) = ImagineBase(基地址)+RVA(相對虛擬地址),你的代碼區塊在內存中就開始與0x00401000h。
?
文件偏移地址:
? ? 因為我們的文件是存儲在磁盤上的,某個數據相對于文件頭的偏移量就是這個數據的偏移地址,稱為文件偏移地址(File Offset)或者物理地址(RAW Offset),偏移地址的起始值是0。
?
MS-DOS頭部(IMAGE_DOS_HEADER):
? ?每個PE文件是以一個DOS程序開始的,還有MZ header之后的DOS stub(DOS塊)。如果這個可執行文件不能被這個系統支持,會打印一串提示符
"This program cannot be run is MS-DOS mode",DOS頭部中主要是WORD e_magic和 LONG e_lfanew這個字段比較重要。這些數據結構可以在winnt.h中找到。
#define IMAGE_DOS_SIGNATURE 0x5A4D #define IMAGE_OS2_SIGNATURE 0x454E #define IMAGE_OS2_SIGNATURE_LE 0x454C #define IMAGE_VXD_SIGNATURE 0x454C #define IMAGE_NT_SIGNATURE 0x00004550#include "pshpack2.h" //這里就是IMAGE_DOS_HEADER的結構了。typedef struct _IMAGE_DOS_HEADER {WORD e_magic; // DOS可執行文件標記“MZ”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; //DOS代碼入口IPWORD e_cs; //DOS代碼的入口CSWORD e_lfarlc;WORD e_ovno;WORD e_res[4];WORD e_oemid;WORD e_oeminfo;WORD e_res2[10];LONG e_lfanew; // 指向PE文件頭, “PE”,0,0} IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;
?LONG e_lfanew是指向PE文件頭的一個地址,它的文件偏移地址是0x3C--也就是60字節開始,然后占據了4個字節,指向PE文件頭,具體看下圖
我們可以看到0x000000F8在偏移60字節的地方,看到PE文件頭在0x000000F8,具體可以自己找一個文件來測試(加深印象)
?DOS文件頭就到這里了,接下來繼續介紹PE頭,也就是IMAGE_NT_HEADER,下面的代碼是對于定義32還是64的PE頭,我們可以看到相應的宏語句
和對應的數據結構定義。
#ifdef _WIN64 //如果采用64的架構typedef IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER;typedef PIMAGE_OPTIONAL_HEADER64 PIMAGE_OPTIONAL_HEADER; #define IMAGE_SIZEOF_NT_OPTIONAL_HEADER IMAGE_SIZEOF_NT_OPTIONAL64_HEADER #define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR64_MAGIC #else /* _WIN64 */ //如果不是采用64而是32位的架構typedef IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER;typedef PIMAGE_OPTIONAL_HEADER32 PIMAGE_OPTIONAL_HEADER; #define IMAGE_SIZEOF_NT_OPTIONAL_HEADER IMAGE_SIZEOF_NT_OPTIONAL32_HEADER #define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR32_MAGIC #endif /* _WIN64 */ //上面的typedef都是改變結構體的名稱typedef struct _IMAGE_NT_HEADERS64 {//這里是64位的PE頭的結構體的定義DWORD Signature;IMAGE_FILE_HEADER FileHeader;IMAGE_OPTIONAL_HEADER64 OptionalHeader;} IMAGE_NT_HEADERS64,*PIMAGE_NT_HEADERS64;typedef struct _IMAGE_NT_HEADERS {//這里是32位的PE頭的結構體的定義DWORD Signature;IMAGE_FILE_HEADER FileHeader;IMAGE_OPTIONAL_HEADER32 OptionalHeader;} IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;
在PE的文件頭中,第一個DWORD Signature, 被定義為了0x00004550h,也就是"PE\0\0"這四個字符。這個標志沒有什么作用。(DOS中指向的地方)。
主要是IMAGE_FILE_HEADER和IMAGE_OPTIONAL_HEADER這兩個結構體中的幾個字段重要。
我么接下來觀看PE文件頭中的IMAGE FILE_HEADER FileHeader這個結構體
typedef struct _IMAGE_FILE_HEADER {WORD Machine; //這里定義的是運行平臺,i386= 0x014Ch這個值,還有其他平臺,看書吧。。WORD NumberOfSections; //這個是標識區塊的數目,緊跟在PE頭的后面,也就是IMAGE_NT_HEADERS的后面DWORD TimeDateStamp;DWORD PointerToSymbolTable;DWORD NumberOfSymbols;WORD SizeOfOptionalHeader; //這里表明了IMAGE_NT_HEADERS中的大小(RAW SIZE),32位一般是0x00E0, 64位PE+一般是0x00F0WORD Characteristics; //普通的EXE是0x010fh, DLL文件是0x210Eh} IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
接下來我們看一下IMAGE_OPTIONAL_HEADER這個結構體,一樣下面的是這個結構體在winnt.h中的定義,下面這個是32位的,還有64位的,但是差不多的,可以看winnt.htypedef struct _IMAGE_OPTIONAL_HEADER
typedef struct _IMAGE_OPTIONAL_HEADER {WORD Magic;BYTE MajorLinkerVersion;BYTE MinorLinkerVersion;DWORD SizeOfCode; //這里定義了包含代碼區塊的大小DWORD SizeOfInitializedData; //這里定義了已經初始化的變量的區塊的大小DWORD SizeOfUninitializedData; //這里是未初始化的變量的區塊的大小DWORD AddressOfEntryPoint; //這里是程序入口的RVA(相對虛擬地址)DWORD BaseOfCode; //這里是程序代碼塊的起始RVADWORD BaseOfData; //這里是數據塊起始RVADWORD ImageBase; //這里是程序默認裝入的基地址(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; //這里定義了數據目錄表的項數,一直保持為16IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //這個是數據目錄表,指向輸入、輸出表、資源塊等數據,很重要} IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;
?這個IMAGE_OPTIONAL_HEADER只需要關注一些關鍵字段就行了,記住。。
接下來我么就看一看這個數據目錄表,數據目錄表簡單點說就是一個長度為16的IMAGE_DATA_DIRECTORY結構體數組而已
typedef struct _IMAGE_DATA_DIRECTORY {DWORD VirtualAddress; //數據塊的其實RVA,很重要DWORD Size; //數據塊的長度} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
這是一個16位的數組,最有一個數組元素作為保留,全部為0,其他的從開頭一直到倒數第二個數據都是已經規定好了的,我們看一下這個數據目錄表成員
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 //Export Table #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 //Import Table 輸入表這里比較重要 #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_ARCHITECTURE 7 #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 #define IMAGE_DIRECTORY_ENTRY_TLS 9 #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 #define IMAGE_DIRECTORY_ENTRY_IAT 12 //IAT (import address table), 這里也很重要 #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14
還有也可以使用LordPE的PE editor來查看這個目錄,現在的查看目錄表的工具很多。。。。。
PE的第一階段到這里,接下來會繼續學習,增加熟悉度。。。。。。。。。。-----------好好學習,天天向上!
?