PE檔案格式詳細解析(一)
一、PE檔案基本介紹
PE檔案是Windows作業系統下使用的一種可執行檔案,由COFF(UNIX平臺下的通用物件檔案格式)格式檔案發展而來。32位成為PE32,64位稱為PE+或PE32+。
二、PE檔案格式
- PE檔案種類如下表所示:
種類 |
主副檔名 |
可執行系列 |
EXE, SCR |
庫系列 |
DLL, OCX, CPL, DRV |
驅動程式系列 |
SYS, VXD |
物件檔案系列 |
OBJ |
基本結構
使用010editor(二進位制檔案檢視工具)開啟一個exe可以看到如下結構:
上圖是該exe檔案的起始部分,也是PE檔案的頭部,exe執行所需要的所有資訊都儲存在PE頭中。
從DOS頭到節區頭是PE頭部分,其下的節區合稱為PE體。檔案中使用偏移(offset),記憶體中使用VA(Virtual Address,虛擬地址)來表示位置。檔案載入到記憶體時,情況就會發生變化(節區大小、位置等)。檔案的內容一般可分為程式碼(.text)、資料(.data)、資源(.rsrc)節,分別儲存。PE頭與各節區的尾部存在一個區域,成為NULL填充。檔案/記憶體中節區的起始位置應該在各檔案/記憶體最小單位的倍數上,空白區域使用NULL進行填充(如上圖所示)。
VA&RVA
VA指程式虛擬記憶體的絕對地址,RVA(Relative Virtual Address,相對虛擬地址)指從某個基準未知(ImageBase)開始的相對地址。VA與RVA的換算滿足如下公式:
RVA + IamgeBase = VA
PE頭內部資訊主要以RVA的形式進行儲存,主要原因是PE檔案(主要是DLL)載入到程式虛擬記憶體的特定位置時, 該位置可能已經載入了其他PE檔案(DLL)。此時需要進行重定位將其載入到其他的空白位置,保證程式的正常執行。
三、PE頭
DOS頭
主要為現代PE檔案可以對早期的DOS檔案進行良好相容存在,其結構體為IMAGE_DOS_HEADER。
大小為64位元組,其中2個重要的成員分別是:
- e_magic:DOS簽名(4D5A,MZ)
- e_lfanew:指示NT頭的偏移(檔案不同,值不同)
DOS存根
stub,位於DOS頭下方,可選,大小不固定,由程式碼與資料混合組成。
NT頭
結構體為IMAGE_NT_HEADERS,大小為F8,由3個成員組成:
- 簽名結構體,值為50450000h(“PE”00)
- 檔案頭,表現檔案大致屬性,結構體為IMAGE_FILE_HEADER,重要成員有4個:
- Machine:每個CPU都擁有的唯一的Machine碼,相容32位Intel x86晶片的Machine碼為14C;
- NumberOfSections:指出檔案中存在的節區數量;
- SizeOfOptionalHeader:指出結構體IMAGE_OPTIONAL_HEADER32(32位系統)的長度
- Characteristics:標識檔案屬性,檔案是否是可執行形態、是否為DLL等,以bit OR形式進行組合
- 可選頭,結構體為IMAGE_OPTIONAL_HEADER32,重要成員有9個:
- Magic:IMAGE_OPTIONAL_HEADER32為10B,IMAGE_OPTIONAL_HEADER64為20B
- AddressOfEntryPoint:持有EP的RVA值,指出程式最先執行的程式碼起始地址
- ImageBase:指出檔案的優先裝入地址(32位程式虛擬記憶體範圍為:0~7FFFFFFF)
- SectionAlignment,FileAlignment:前者制定了節區在記憶體中的最小單位,後者制定了節區在磁碟檔案中的最小單位
- SizeOfImage:指定了PE Image在虛擬記憶體中所佔空間的大小
- SizeOfHeaders:指出整個PE頭的大小
- Subsystem:區分系統驅動檔案和普通可執行檔案
- NumberOfRvaAndSize:指定DataDirectory陣列的個數
- DataDirectory:由IMAGE_DATA_DIRECTORY結構體組成的陣列
節區頭
節區頭中定義了各節區的屬性,包括不同的特性、訪問許可權等,結構體為IMAGE_SECTION_HEADER,重要成員有5個:
- VirtualSize:記憶體中節區所佔大小
- VirtualAddress:記憶體中節區起始地址(RVA)
- SizeOfRawData:磁碟檔案中節區所佔大小
- Charateristics:節區屬性(bit OR)
四、RVA To RAW
PE檔案從磁碟到記憶體的對映:
查詢RVA所在節區
使用簡單的公式計算檔案偏移:
RAW - PointerToRawData = RVA - ImageBase
RAW = RVA - ImageBase + PointerToRawData
example:ImageBase為0x10000000,節區為.text,檔案中起始地址為0x00000400,記憶體中的起始地址為0x01001000,RVA = 5000,RAW = 5000 - 1000 + 400 = 4400。