pe結構之初體驗(一)我已經講了dos及NT的Signature字段和IMAGE_FILE_HEADER結構。
在pe結構之初體驗(二)中我將接着講IMAGE_OPTIONAL_HEADER32結構
struct _IMAGE_OPTIONAL_HEADER {
18h: WORD Magic; //01 0B //(!重要) |10B-32位下的PE文件
1Ah: BYTE MajorLinkerVersion; //AA
1Bh: BYTE MinorLinkerVersion; //AA
1Ch: DWORD SizeOfCode; //AA AA AA AA
20h: DWORD SizeOfInitializedData; //AA AA AA AA
24h: DWORD SizeOfUninitializedData; //AA AA AA AA
28h: DWORD AddressOfEntryPoint; //00 00 00 02 //(!重要) |程序入口點(02,斷在CC處)
2Ch: DWORD BaseOfCode; //AA AA AA AA
30h: DWORD BaseOfData; //AA AA AA AA
34h: DWORD ImageBase; //00 40 00 00 //(!重要) |內存鏡像基址
38h: DWORD SectionAlignment; //00 00 10 00 //(!重要) |內存對齊
3Ch: DWORD FileAlignment; //00 00 02 00 //(!重要) |文件對齊
40h: WORD MajorOperatingSystemVersion; //AA AA
42h: WORD MinorOperatingSystemVersion; //AA AA
44h: WORD MajorImageVersion; //AA AA
46h: WORD MinorImageVersion; //AA AA
48h: WORD MajorSubsystemVersion; //00 04 //(!重要) |子系統版本號
WORD MinorSubsystemVersion; //AA AA
DWORD Win32VersionValue; //AA AA AA AA
DWORD SizeOfImage; //00 00 20 00 //(!重要) |PE文件映射到內存後的尺寸,SectionAlignment的倍數
DWORD SizeOfHeaders; //00 00 02 00 //(!重要) |所有頭+節表按照文件對齊後的大小
DWORD CheckSum; //AA AA AA AA
WORD Subsystem; //00 02 //(!重要) |子系統
WORD DllCharacteristics; //00 00 //(!重要) |
DWORD SizeOfStackReserve; //00 40 00 00 //(!重要) |初始化時保留的棧大小(桟最大值)
DWORD SizeOfStackCommit; //00 00 10 00 //(!重要) |初始化時實際提交的棧大小(實際使用桟大小)
DWORD SizeOfHeapReserve; //00 10 00 00 //(!重要) |初始化時保留的堆大小(堆最大值)
DWORD SizeOfHeapCommit; //00 01 00 00 //(!重要) |初始化時實際提交的堆大小(實際使用堆大小)
DWORD LoaderFlags; //AA AA AA AA
DWORD NumberOfRvaAndSizes; //00 00 00 04 //(!重要) |目錄項數目(4),其實最優是2項,有導入表即可
_IMAGE_DATA_DIRECTORY DataDirectory[16]; //目錄項(4個目錄項,先全初始化爲0)
};
AddressOfEntryPoint字段,指出文件被執行時的入口地址,這是一個RVA地址(相對虛擬地址)。這個域表示應用程序入口點的位置。並且,對於系統黑客來說,這個位置就是導入地址表(IAT)的末尾。
ImageBase字段:進程映像地址空間中的首選基地址(文件的優先裝入地址)。Windows NT的Microsoft Win32 SDK鏈接器將exe文件這個值默認設爲0x00400000(而DLL默認爲10000000h),但是你可以使用-BASE:linker開關改變這個值。(如果裝入地址被佔據,則由Windows裝載器改架到其它地方,所以就需要進行重定位操作,影響效率。對於EXE文件因爲它總是使用獨立的虛擬地址空間,所以裝入地址不可能被佔用,而DLL文件則必須包含重定位信息了)
SectionAlignment字段:內存中的區塊的對齊大小。Windows NT虛擬內存管理器規定,段對齊不能少於頁尺寸(當前的x86平臺是4096字節),並且必須是成倍的頁尺寸。4096字節是x86鏈接器的默認值,但是它可以通過-ALIGN: linker開關來設置。 (32位位1000h,即4kb;64位爲2000h,即8kb。而一般我們的程序兼容或者不兼容,就是因爲這個對齊大小不一樣)
FileAlignment字段:文件(磁盤)中的區塊大小。映像文件首先裝載的最小的信息塊間隔。例如,鏈接器將一個段實體(段的原始數據)加零擴展爲文件中最接近的FileAlignment邊界。早先提及的2.39版鏈接器將映像文件以0x200字節的邊界對齊,這個值可以被強制改爲512到65535這麼多。 (一般情況下32位爲200h,即200個字節)
NumberOfRvaAndSizes字段。這個域標識了接下來的DataDirectory數組。請注意它被用來標識這個數組,而不是數組中的各個入口數字,這一點非常重要。(自Windows NT發佈以來一直都是16)
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]。
即DataDirectory字段。數據目錄表示文件中其它可執行信息重要組成部分的位置。它事實上就是一個由16個相同的IMAGE_DATA_DIRECTORY結構的數組組成(結構裏面的結構的結構),位於可選頭部結構的末尾。當前的PE文件格式定義了16種可能的數據目錄,這之中的11種現在在使用中。
IMAGE_DATA_DIRECTORY結構的定義:
IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress DWORD?; //數據的起始RVA
isize DWORD?; //數據塊的長度
IMAGE_DATA_DIRECTORY ENDS
數據目錄列表的含義:
一、PE文件到內存的映射
(1)在執行一個PE文件的時候,windows 並不在一開始就將整個文件讀入內存的,而是採用與內存映射文件類似的機制。也就是說,windows 裝載器在裝載的時候僅僅建立好虛擬地址和PE文件之間的映射關係。當且僅當真正執行到某個內存頁中的指令或者訪問某一頁中的數據時,這個頁面纔會被從磁盤提交到物理內存,這種機制使文件裝入的速度和文件大小沒有太大的關係。
(2)但是要注意的是,系統裝載可執行文件的方法又不完全等同於內存映射文件。當使用內存映射文件的時候,系統對“原著”相當忠實,如果將磁盤文件和內存映像比較的話,可以發現不管是數據本身還是數據之間的相對位置它丫丫的都是完全相同的。而我們知道,在裝載可執行文件的時候,有些數據在裝入前會被預處理,如重定位等,正因此,裝入以後,數據之間的相對位置可能發生微妙的變化。
(3)Windows 裝載器在裝載DOS部分、PE文件頭部分和節表(區塊表)部分是不進行任何特殊處理的,而在裝載節(區塊)的時候則會自動按節(區塊)的屬性做不同的處理。
一般情況下,它會處理以下幾個方面的內容:
內存頁的屬性;
節的偏移地址;
節的尺寸;
不進行映射的節。
二、對節(區塊)的處理
(1)內存頁的屬性:
對於磁盤映射文件來說,所有的頁都是按照磁盤映射文件函數指定的屬性設置的。但是在裝載可執行文件時,與節對應的內存頁屬性要按照節的屬性來設置。所以,在同屬於一個模塊的內存頁中,從不同節映射過來的的內存頁的屬性是不同的。
(2)節的偏移地址:
1.節的起始地址在磁盤文件中是按照 IMAGE_OPTIONAL_HEADER32 結構的 FileAlignment 字段的值進行對齊的,而當被加載到內存中時是按照同一結構中的 SectionAlignment 字段的值對其的,兩者的值可能不同,所以一個節被裝入內存後相對於文件頭的偏移和在磁盤文件中的偏移可能是不同的。
2.注意,節事實上就是相同屬性數據的組合!當節被裝入到內存中的時候,相同一個節所對應的內存頁都將被賦予相同的頁屬性, 事實上,Windows 系統對內存屬性的設置是以頁爲單位進行的,所以節在內存中的對齊單位必須至少是一個頁的大小。(小甲魚溫馨提示:對於32位操作系統來說,這個值一般是4KB==1000H; 對於64位操作系統這個值一般是8KB==2000H)
3.在磁盤中就沒有這個限制,因爲在磁盤中排放是以什麼爲主?肯定是以空間爲主導,在磁盤只是存放,不是使用,所以不用設置那麼詳細的屬性。試想想看,如果在磁盤中都是以4KB爲大小對齊的話,不夠就用0來填充,那麼一個只佔20字節的數據就要消耗4KB的空間來存放,是不是浪費?有木有??
(3)節的尺寸:
對節的尺寸的處理主要分爲兩個方面:
第一個方面,正如剛剛我們所講的,由於磁盤映像和內存映像中節對齊存儲單位的不同而導致了長度擴展不同(填充的0數量不同嘛~);
第二個方面,是對於包含未初始化數據的節的處理問題。既然是未初始化,那麼沒有必要爲其在磁盤中浪費空間資源,但在內存中不同,因爲程序一運行,之前未初始化的數據便有可能要被賦值初始化,那麼就必須爲他們留下空間。
(4)不進行映射的節:
有些節並不需要被映射到內存中,例如.reloc節,重定位數據對於文件的執行代碼來說是透明的,無作用的,它只是提供Windows 裝載器使用,執行代碼根本不會去訪問到它們,所以沒有必要將他們映射到物理內存中。(重定位在上一講PE可選頭中的ImageBase字段粗略講過大致意思,後面會具體講的)
三、節表(區塊表)
(1)PE文件中所有節的屬性都被定義在節表中,節表由一系列的IMAGE_SECTION_HEADER結構排列而成,每個結構用來描述一個節,結構的排列順序和它們描述的節在文件中的排列順序是一致的。全部有效結構的最後以一個空的IMAGE_SECTION_HEADER結構作爲結束,所以節表中總的IMAGE_SECTION_HEADER結構數量等於節的數量加一。節表總是被存放在緊接在PE文件頭的地方。
(2)另外,節表中 IMAGE_SECTION_HEADER 結構的總數總是由PE文件頭 IMAGE_NT_HEADERS 結構中的 FileHeader.NumberOfSections 字段來指定的。
typedef struct _IMAGE_SECTION_HEADER
{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 節表名稱,如“.text”
//IMAGE_SIZEOF_SHORT_NAME=8
union
{
DWORD PhysicalAddress; // 物理地址
DWORD VirtualSize; // 真實長度,這兩個值是一個聯合結構,可以使用其中的任何一個,一
// 般是取後一個
} Misc;
DWORD VirtualAddress; // 節區的 RVA 地址
DWORD SizeOfRawData; // 在文件中對齊後的尺寸
DWORD PointerToRawData; // 在文件中的偏移量
DWORD PointerToRelocations; // 在OBJ文件中使用,重定位的偏移
DWORD PointerToLinenumbers; // 行號表的偏移(供調試使用地)
WORD NumberOfRelocations; // 在OBJ文件中使用,重定位項數目
WORD NumberOfLinenumbers; // 行號表中行號的數目
DWORD Characteristics; // 節屬性如可讀,可寫,可執行等
}IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;