PE可執行文件格式詳解

作者:SnowMzn

PE

  • PE是目前Windows平台上主流的可執行文件格式,包括常見的可執行程序EXE文件、動態鏈接庫DLL文件等。
  • 學習PE文件格式對於了解windows操作系統是必不可少的。

與PE有關的基本概念

地址

  • 虛擬內存地址(Virtual Address, VA)PE文件中的指令被裝入內存後的地址。
  • 相對虛擬內存地址(Reverse Virtual Address, RVA相對虛擬地址是內存地址相對於映射基址的偏移量。
  • 文件偏移地址(File Offset Address, FOA)數據在PE文件中的地址叫文件偏移地址,這是文件在磁碟上存放時相對於文件開頭的偏移。
  • 裝在基址(Image base)PE裝入內存時的基地址。默認情況下,EXE文件在內存中的基地址時0x00400000, DLL文件是0x10000000。這些位置可以通過修改編譯選項更改。
  • 虛擬內存地址、映射基址、相對虛擬內存地址的關係:

VA = Image Base + RVAn

  • 文件偏移是相對於文件開始處0位元組的偏移,相對虛擬地址則是相對於裝載基址0x00400000處的偏移。(1)PE文件中的數據按照磁碟數據標準存放,以0x200位元組為基本單位進行組織,PE數據節的大小永遠是0x200的整數倍。(2)當代碼裝入內存後,將按照內存數據標準存放,並以0x1000位元組為基本單位進行組織,內存中的節總是0x1000的整數倍。
  • 內存中數據節相對於裝載基址的偏移量和文件中數據節的偏移量的差異稱為節偏移。

文件偏移地址 = 虛擬內存地址(VA) - 裝載基址(Image Base) - 節偏移 n = RVA - 節偏移n

MS-DOS頭部解析

  • PE文件的第一個位元組起始於MS-DOS頭部,被稱作 IMAGE_DOS_HEADER。

IMAGE_DOS_HEADER {n WORD e_magic; // +0000h - EXE標誌,「MZ」n WORD e_cblp; // +0002h - 最後(部分)頁中的位元組數n WORD e_cp; // +0004h - 文件中的全部和部分頁數n WORD e_crlc; // +0006h - 重定位表中的指針數n WORD e_cparhdr; // +0008h - 頭部尺寸,以段落為單位n WORD e_minalloc; // +000ah - 所需的最小附加段n WORD e_maxalloc; // +000ch - 所需的最大附加段n WORD e_ss; // +000eh - 初始的SS值(相對偏移量)n WORD e_sp; // +0010h - 初始的SP值n WORD e_csum; // +0012h - 補碼校驗值n WORD e_ip; // +0014h - 初始的IP值n WORD e_cs; // +0016h - 初始的CS值n WORD e_lfarlc; // +0018h - 重定位表的位元組偏移量n WORD e_ovno; // +001ah - 覆蓋號n WORD e_res[4]; // +001ch - 保留字00n WORD e_oemid; // +0024h - OEM標識符n WORD e_oeminfo; // +0026h - OEM信息n WORD e_res2[10]; // +0028h - 保留字n LONG e_lfanew; // +003ch - PE頭相對於文件的偏移地址n }n

  • DOS頭的下面是DOS Stub。整個DOS Stub是一個位元組塊,其內容隨著鏈接時使用的鏈接器不同而不同,PE中並沒有與之對應的相關結構。

PE頭部解析

  • 緊跟在DOS stub後面的是PE頭標識Signature。與大部分文件格式的頭部結構一樣,PE頭部信息中有一個四位元組的標識,其內容固定,對應的ASCII碼的字元串是「PE00」。

PE頭IMAGE_NT_HEADERS

  • IMAGE_NT_HEADERS是廣義上的PE頭,在標準的PE文件中其大小為456個位元組,由4個位元組的PE標識符 + IMAGE_FILE_HEADER + IMAGE_OPTIONAL_HEADER32組成。

IMAGE_NT_HEADERS {n DWORD Signature; // +0000h - PE文件標識,「PE00」n IMAGE_FILE_HEADER FileHeader; // +0004h - PE標準頭n IMAGE_OPTIONAL_HEADER32 OptionalHeader; // +0018h - PE擴展頭n}n

標準PE頭IMAGE_FILE_HEADER

  • 標準PE頭IMAGE_FILE_HEADER緊跟在PE頭標識後,即位於IMAGE_DOS_HEADER的e_lfanew + 4的位置。由此位置開始的20個位元組為數據結構標準PE頭IMAGE_FILE_HEADER的內容。它記錄了PE文件的全局屬性,如該PE文件運行的平台、PE文件類型、文件中存在的節的總數等。

IMAGE_FILE_HEADER {n WORD Machine; // +0004h - 運行平台n WORD NumberOfSections; // +0006h - PE中節的數量n DWORD TimeDateStamp; // +0008h - 文件創建日期和時間n DWORD PointerToSymbolTable; // +000ch - 指向符號表n DWORD NumberOfSymbols; // +0010h - 符號表中的符號數量n WORD SizeOfOptionalHeader; // +0014h - 擴展頭結構的長度n WORD Characteristics; // +0016h - 文件屬性nn}n

擴展PE頭IMAGE_OPTIONAL_HEADER

  • 文件執行時的入口地址、文件被操作系統裝入內存後的默認基地址,以及節在磁碟和內存中的對齊單位等信息均可以在此結構中找到。對該結構中的某些數值的隨意改動可能會造成PE文件的載入或運行失敗。

IMAGE_OPTIONAL_HEADER {n WORD Magic; // +0018h - 魔術字107h = ROM Image,10bh = exe Imagen BYTE MajorLinkerVersion; // +001ah - 鏈接器版本號n BYTE MinorLinkerVersion; // +001bh - n DWORD SizeOfCode; // +001ch - 所有含代碼的節的總大小n DWORD SizeOfInitializedData; // +0020h - 所有含已初始化數據的節的總大小n DWORD SizeOfUninitializedData; // +0024h - 所有含未初始化數據的節的大小n DWORD AddressOfEntryPoint; // +0028h - 程序執行入口RVAn DWORD BaseOfCode; // +002ch - 代碼的節的起始RVAn DWORD BaseOfData; // +0030h - 數據的節的起始RVAn DWORD ImageBase; // +0034h - 程序的建議裝載地址n DWORD SectionAlignment; // +0038h - 內存中的節的對齊粒度n DWORD FileAlignment; // +003ch - 文件中的節的對齊粒度n WORD MajorOperatingSystemVersion; // +0040h - 操作系統版本號n WORD MinorOperatingSystemVersion; // +0042h - n WORD MajorImageVersion; // +0044h - 該PE的版本號n WORD MinorImageVersion; // +0046h - n WORD MajorSubsystemVersion; // +0048h - 所需子系統的版本號n WORD MinorSubsystemVersion; // +004ah - n DWORD Win32VersionValue; // +004ch - 未用n DWORD SizeOfImage; // +0050h - 內存中的整個PE映象尺寸n DWORD SizeOfHeaders; // +0054h - 所有頭+節表的大小n DWORD CheckSum; // +0058h - 校驗和n WORD Subsystem; // +005ch - 文件的子系統n WORD DllCharacteristics; // +005eh - DLL文件特性n DWORD SizeOfStackReserve; // +0060h - 初始化時的棧大小n DWORD SizeOfStackCommit; // +0064h - 初始化時實際提交的棧大小n DWORD SizeOfHeapReserve; // +0068h - 初始化時保留的堆大小n DWORD SizeOfHeapCommit; // +006ch - 初始化時實際提交的堆大小n DWORD LoaderFlags; // +0070h - 與調試有關n DWORD NumberOfRvaAndSizes; // +0074h - 下面的數據目錄結構的項目數量n IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 0078h - 數據目錄n}n

數據目錄項IMAGE_DATA_DIRECTORY

  • IMAGE_OPTIONAL_HEADER結構的最後一個欄位為DataDirectory。
  • 該欄位定義了PE文件中出現的所有不同類型的數據的目錄信息,從Windows NT 3.1操作系統開始到現在,該數據目錄中定義的數據類型一直是16種。
  • PE中使用了一種稱作「數據目錄項IMAGE_DATA_DIRECTORY」的數據結構來定義每種數據。

IMAGE_DATA_DIRECTORY {n DWORD VirtualAddress; // +0000h - 數據的起始RVAn DWORD Size; // +0004h - 數據塊的長度n}n

  • 總的數據目錄一共由16個相同的IMAGE_DATA_DIRECTORY結構連續排列在一起組成。
  • 如果想在PE文件中尋找特定類型的數據,就需要從該結構開始。
  • 這16個元組的數組每一項均代表PE中的某一個類型的數據,各數據類型為:

數組編號中文描述0導出表地址和大小1導入表地址和大小2資源表地址和大小3異常表地址和大小4屬性證書數據地址和大小5基地址重定位表地址和大小6調試信息地址和大小7預留為08指向全局指針寄存器的值9線程局部存儲地址和大小10載入配置表地址和大小11綁定導入表地址和大小12導入函數地址表地址和大小13延遲導入表地址和大小14CLR運行時頭部數據地址和大小15系統保留

節表項IMAGE_SECTION_HEADER

PE頭IMAGE_NT_HEADERS後緊跟著節表。它由許多個節表項(IMAGE_SECTION_HEADER)組成,每個節表項記錄了PE中與某個特定的節有關的信息,如節的屬性、節的大小、在文件和內存中的起始位置等。節表中節的數量由欄位IMAGE_FILE_HEADER.NumberOfSection來定義。

IMAGE_SECTION_HEADER {n BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // +0000h - 8個位元組節名n union {n DWORD PhysicalAddress;n DWORD VirtualSize;n } Misc; // +0008h - 節區的尺寸n DWORD VirtualAddress; // +000ch - 節區的RVA地址n DWORD SizeOfRawData; // +0010h - 在文件中對齊後的尺寸n DWORD PointerToRawData; // +0014h - 在文件中的偏移n DWORD PointerToRelocations; // +0018h - 在OBJ文件中使用n DWORD PointerToLinenumbers; // +001ch - 行號表的位置(供調試用)n WORD NumberOfRelocations; // +0020h - 在OBJ文件中使用n WORD NumberOfLinenumbers; // +0022h - 行號表中行號的數量n DWORD Characteristics; // +0024h - 節的屬性n}n

節表後面就是節的內容。截至節表,以上內容就是PE文件頭部涉及的所有數據結構。

推薦閱讀:

TAG:PE文件格式 | 原理 |