PE格式,是Windows的可執(zhí)行檔的格式。 Windows中的 exe檔,dll檔,都是PE格式。 PE 就是Portable Executable 的縮寫。 Portable 是指對(duì)於不同的Windows版本和不同的CPU類型上PE檔的格式是一樣的,當(dāng)然CPU不一樣了,CPU指令的二進(jìn)位編碼是不一樣的。只是檔中各種東西的佈局是一樣的。 能告示根底嗎! 摘要 Matt Pietrek(姜慶東譯) 對(duì)可執(zhí)行檔的深入認(rèn)識(shí)將帶你深入到系統(tǒng)深處。如果你知道你的exe/dll裏是些什麼東東,你就是一個(gè)更有知識(shí)的程式師。作為系列文章的第一章,將關(guān)注這幾年來(lái)PE格式的變化,同時(shí)也簡(jiǎn)單介紹一下PE格式。經(jīng)過(guò)這次更新,作者加入了PE格式是如何與.NET協(xié)作的及PE檔表格(PE FILE SECTIONS),RVA,The DataDirectory,函數(shù)的輸入等內(nèi)容。 ==================== 很久以前,我給Microsoft Systems Journal(現(xiàn)在的MSDN)寫了一篇名為“Peering Inside the PE: A Tour of the Win32 Portable Executable File format”的文章。後來(lái)比我期望的還流行,到現(xiàn)在我還聽說(shuō)有人在用它(它還在MSDN裏)。不幸的是,那篇文章的問(wèn)題依舊存在,WIN32的世界靜悄悄地變了好多,那篇文章已顯得過(guò)期了。從這個(gè)月開始我將用這兩篇文章來(lái)彌補(bǔ)。 你可能會(huì)問(wèn)為什麼我應(yīng)當(dāng)瞭解PE格式,答案依舊:作業(yè)系統(tǒng)的可執(zhí)行檔格式和資料結(jié)構(gòu)暴露出系統(tǒng)的底層細(xì)節(jié)。通過(guò)瞭解這些,你的程式將編的更出色。 當(dāng)然,你可以閱讀微軟的文檔來(lái)瞭解我將要告訴你的。但是,像很多文檔一樣,‘寧可晦澀,但為瓦全’。 我把焦點(diǎn)放在提供一些不適合放在正式文檔裏的內(nèi)容。另外,這篇文章裏的一些知識(shí)不見得能在官方文檔裏找到。 1. 裂縫的撕開 讓我給你一些從1994年我寫那篇文章來(lái)PE格式變化的例子。WIN16已經(jīng)成為歷史,也就沒(méi)有必要作什麼比較和說(shuō)明了。另外一個(gè)可憎的東西就是用在WINDOWS 3.1 中的WIN32S,在它上面運(yùn)行程式是那麼的不穩(wěn)定。 那時(shí)候,WINDOWS 95(也叫Chicago)還沒(méi)有發(fā)行。NT還是3.5版。微軟的連接器還沒(méi)開始大規(guī)模的優(yōu)化,儘管如此,there were MIPS and DEC Alpha implementations of Windows NT that added to the story. 那麼究竟,這麼些年來(lái),有些什麼新的東西出來(lái)呢?64位的WINDOWS有了它自己的PE變種,WINDOWS CE 支持各種CPU了,各種優(yōu)化如DLL的延遲載入,節(jié)表的合併,動(dòng)態(tài)捆綁等也已出臺(tái)。 有很多類似的東西發(fā)生了。 讓我們最好忘了.NET。它是如何與系統(tǒng)切入的呢?對(duì)於作業(yè)系統(tǒng),.NET的可執(zhí)行檔格式是與舊的PE格式相容的。雖然這麼說(shuō),在運(yùn)行時(shí)期,.NET還是按元資料和中間語(yǔ)言來(lái)組織資料的,這畢竟是它的核心。這篇文章當(dāng)中,我將打開.NET元資料這扇門,但不做深入討論。 如果WIN32的這些變化都不足以讓我重寫這篇文章,就是原來(lái)的那些錯(cuò)誤也讓我汗顏。比如我對(duì)TLS的描述只是一帶而過(guò),我對(duì)時(shí)間戳的描述只有你生活在美國(guó)西部才行等等。還有,一些東西已是今是作非了,我曾說(shuō)過(guò).RDATA幾乎沒(méi)排上用場(chǎng),今天也是,我還說(shuō)過(guò).IDATA節(jié)是可讀可寫的,但是一些搞API攔截的人發(fā)現(xiàn)好像是錯(cuò)的。 在更新這篇文章的過(guò)程當(dāng)中,我也檢查了PEDUMP這個(gè)用來(lái)傾印PE檔的程式.這個(gè)程式能夠在0X86和IA-64平臺(tái)下編譯和運(yùn)行。 2. PE格式概覽 微軟的可執(zhí)行檔格式,也就是大家熟悉的PE 格式,是官方文檔的一部分。但是,它是從VAX/VMS上的COFF派生出來(lái)的,就WINDOWS NT小組的大部分是從DEC轉(zhuǎn)過(guò)來(lái)的看來(lái),這是可以理解的。很自然,這些人在NT的開發(fā)上會(huì)用他們以往的代碼。 採(cǎi)用術(shù)語(yǔ)“PORTABLE EXECUTABLE”是因?yàn)槲④浵M幸粋€(gè)通用在所有WINDOWS平臺(tái)上和所有CPU上的檔格式。從大的方面講,這個(gè)目標(biāo)已經(jīng)實(shí)現(xiàn)。它適用于NT及其後代,95及其後代,和CE. 微軟產(chǎn)生的OBJ檔是用COFF格式的。當(dāng)你看到它的很多域都是用八進(jìn)制的編碼的,你會(huì)發(fā)現(xiàn)她是多麼古老了。COFF OBJ檔用到了很多和PE一樣的資料結(jié)構(gòu)和枚舉,我馬上會(huì)提到一些。 64位的WINDOWS只對(duì)PE格式作了一點(diǎn)點(diǎn)改變。這個(gè)新的格式叫做PE32+。沒(méi)有增加一個(gè)欄位,且只刪了一個(gè)欄位。其他的改變就是把以前的32位欄位擴(kuò)展成64位。對(duì)於C++代碼,通過(guò)巨集定義WINDOWS的頭檔已經(jīng)遮罩了這些差別。 EXE與DLL的差別完全是語(yǔ)義上的。它們用的都是同樣一種檔格式-PE。唯一的區(qū)別就是其中有一個(gè)欄位標(biāo)識(shí)出是EXE還是DLL.還有很多DLL的擴(kuò)展比如OCX,CPL等都是DLL.它們有一樣的實(shí)體。 你首先要知道的關(guān)於PE的知識(shí)就是磁片中的資料結(jié)構(gòu)佈局和記憶體中的資料結(jié)構(gòu)佈局是一樣的。載入可執(zhí)行檔(比如LOADLIBARY)的首要任務(wù)就是把磁片中的檔映射到進(jìn)程的位址空間.因此像IMAGE_NT_HEADER(下面解釋)在磁片和記憶體中是一樣的。關(guān)鍵的是你要懂得你怎樣在磁片中獲得PE檔某些資訊的,當(dāng)它載入記憶體時(shí)你可以一樣獲得,基本上是沒(méi)什麼不同的(即記憶體映射檔)。但是知道與映射普通的記憶體映射檔不同是很重要的。WINDOWS載入器察看PE檔才決定映射到哪里,然後從檔的開始處往更高的位址映射,但是有的東西在檔中的偏移和在記憶體中的偏移會(huì)不一樣。儘管如此,你也有了足夠的資訊把檔偏移轉(zhuǎn)化成記憶體偏移。見圖一: 當(dāng)Windows載入器把PE載入記憶體,在記憶體中它稱作模組(MODULE),檔從HMODULE這個(gè)位址開始映射。記住這點(diǎn):給你個(gè)HMODULE,從那你可以知道一個(gè)資料結(jié)構(gòu)(IMAGE_DOS_HEADER),然後你還可以知道所有得資料結(jié)構(gòu)。這個(gè)強(qiáng)大的功能對(duì)於API攔截特別有意義。(準(zhǔn)確地說(shuō):對(duì)於WINDOWS CE,這是不成立的,不過(guò)這是後話)。 記憶體中的模組代表著進(jìn)程從這個(gè)可執(zhí)行檔中所需要的所有代碼,資料,資源。其他部分可以被讀入,但是可能不映射(如,重定位節(jié))。還有一些部分根本就不映射,比如當(dāng)調(diào)試資訊放到檔的尾部的時(shí)候。有一個(gè)欄位告訴系統(tǒng)把檔映射到記憶體需要多少記憶體。不需要的資料放在檔的尾部,而在過(guò)去,所有部分都映射。 在WINNT.H描述了PE 格式。在這個(gè)檔中,幾乎有所有的關(guān)於PE的資料結(jié)構(gòu),枚舉,#DEFINE。當(dāng)然,其他地方也有相關(guān)文檔,但是還是WINNT.H說(shuō)了算。 有很多檢測(cè)PE文件的工具,有VISUAL STUDIO的DUMPBIN,SDK中的DEPENDS,我比較喜歡DEPENDS,因?yàn)樗砸环N簡(jiǎn)潔的方式檢測(cè)出檔的引入引出。一個(gè)免費(fèi)的PE察看器,PEBrowse,來(lái)自smidgenosoft。我的pedump也是很有用的,它和dumpbin有一樣的功能。 從api的立場(chǎng)看,imagehlp.dll提供了讀寫pe檔的機(jī)制。 在開始討論pe檔前,回顧一下pe檔的一些基本概念是有意義的。在下面幾節(jié),我將討論:pe 節(jié),相對(duì)虛擬位址(rva),資料目錄,函數(shù)的引入。 3. PE節(jié) PE節(jié)以某鍾順序表示代碼或資料。代碼就是代碼了,但是卻有多種類型的資料,可讀寫的程式資料(如總體變數(shù)),其他的節(jié)包含API的引入引出表,資源,重定位。每個(gè)節(jié)有自己的屬性,包括是否是代碼節(jié),是否唯讀還是可讀可寫,節(jié)的資料是否全局共用。 通常,節(jié)中的資料邏輯上是關(guān)聯(lián)的。PE檔一般至少要有兩個(gè)節(jié),一個(gè)是代碼,另一個(gè)為資料。一般還有一個(gè)其他類型的資料的節(jié)。後面我將描述各種類型的節(jié)。 每個(gè)節(jié)都有一個(gè)獨(dú)特的名字。這個(gè)名字是用來(lái)傳達(dá)這個(gè)節(jié)的用途的。比如,.RDATA表示一個(gè)唯讀節(jié),節(jié)的名字對(duì)於作業(yè)系統(tǒng)毫無(wú)意義,只是為了人們便於理解。把一個(gè)節(jié)命名為FOOBAR和.TEXT是一樣有用的。微軟給他們的節(jié)命名了個(gè)有特色的名字,但是這不是必需的。Borland的連接器用的是code和data 一般編譯器將產(chǎn)生一系列標(biāo)準(zhǔn)的節(jié),但這沒(méi)有什麼不可思議的。你可以建立和命名自己的節(jié),連接器會(huì)自動(dòng)在程式檔中包含它們。在visual c++中,你能用#pragma指令讓編譯器插入資料到一個(gè)節(jié)中。像下面這樣: #pragma data_seg("MY_DATA") ...有必要初始化 #pragma data_seg() 你也可以對(duì).data做同樣的事。大部分的程式都只用編譯器產(chǎn)生的節(jié),但是有時(shí)候你卻需要這樣。比如建立一個(gè)全局共用節(jié)。 節(jié)並不是全部由連接器確定的,他們可以在編譯階段由編譯器放入obj檔。連接器的工作就是合併所有obj和庫(kù)中需要的節(jié)成一個(gè)最終的合適的節(jié)。比如,你的工程中的所有obj可能都有一個(gè)包含代碼的.text節(jié),連接器把這些節(jié)合併成一個(gè).text節(jié)。同樣對(duì)於.data等。這些主題超出了這篇文章的範(fàn)圍了。還有更多的規(guī)則關(guān)於連接器的。在obj文件中是專門給linker用的,並不放入到pe檔中,這種節(jié)是用來(lái)給連接器傳遞資訊的。 節(jié)有兩個(gè)關(guān)於對(duì)齊的欄位,一個(gè)對(duì)應(yīng)磁片檔,另一個(gè)對(duì)應(yīng)記憶體中的檔。Pe檔頭指出了這兩個(gè)值,他們可以不一樣。每個(gè)節(jié)的偏移從對(duì)齊值的倍數(shù)開始。比如,典型的對(duì)齊值是0x200,那麼每個(gè)節(jié)的的偏移必須是0x200的倍數(shù)。一旦載入記憶體,節(jié)的起始位址總是以頁(yè)對(duì)齊。X86cpu的頁(yè)大小為4k,al-64為8k。 下麵是pedump傾印出的Windows XP KERNEL32.DLL.的.text .data節(jié)的信息: Section Table 01 .text VirtSize: 00074658 VirtAddr: 00001000 raw data offs: 00000400 raw data size: 00074800 ... 02 .data VirtSize: 000028CA VirtAddr: 00076000 raw data offs: 00074C00 raw data size: 00002400 建立一個(gè)節(jié)在檔中的偏移和它相對(duì)於載入位址的偏移相同的pe檔是可能的。在98/me中,這會(huì)加速大檔的載入。Visual studio 6.0 的默認(rèn)選項(xiàng) /opt:win98j就是這樣產(chǎn)生檔的。在Visual studio.net中是否用/opt:nowin98取決於檔是否夠小。 一個(gè)有趣的連接器特徵是合併節(jié)的能力。如果兩個(gè)節(jié)有相似相容的屬性,連接的時(shí)候就可以合併為一個(gè)節(jié)。這取決於是否用/merger開關(guān)。像下麵就把.rdata和.text合併為一個(gè)節(jié).text /MERGE:.rdata=.text 合併節(jié)的優(yōu)點(diǎn)就是對(duì)於磁片和記憶體節(jié)省空間。每個(gè)節(jié)至少佔(zhàn)用一頁(yè)記憶體,如果你可以把可執(zhí)行檔的節(jié)數(shù)從4減到3,很可能就可以少用一頁(yè)記憶體。當(dāng)然,這取決於兩個(gè)節(jié)的空餘空間加起來(lái)是否達(dá)到一頁(yè)。 當(dāng)你合併節(jié)事情會(huì)變得有意思,因?yàn)檫@沒(méi)有什麼硬性和容易的規(guī)則。比如你可以合併.rdata到.text, 但是你不可以把.rsrc.reloc.pdata合併到別的節(jié)。先前Visual Studio .NET允許把.idata合併,後來(lái)又不允許了。但是當(dāng)發(fā)行的時(shí)候,連接器還是可以把.idata合併到別的節(jié)。 因?yàn)橐牍?jié)的一部分在載入器載入時(shí)將被寫入,你可能驚奇它是如何被放入一個(gè)唯讀節(jié)的。是這樣的,在載入的時(shí)候系統(tǒng)會(huì)臨時(shí)改變那些包含引入節(jié)的頁(yè)為可讀可寫,初始化完成後,又恢復(fù)原來(lái)屬性。 4. 相對(duì)虛擬位址 在可執(zhí)行檔中,有很多地方需要指定記憶體位址,比如,引用總體變數(shù)時(shí),需要指定它的位址。Pe檔儘管有一個(gè)首選的載入位址,但是他們可以載入到進(jìn)程空間的任何地方,所以你不能依賴於pe的載入點(diǎn)。由於這點(diǎn),必須有一個(gè)方法來(lái)指定位址而不依賴於pe載入點(diǎn)的地址。為了避免把記憶體位址硬編碼進(jìn)pe檔,提出了RVA。RVA是一個(gè)簡(jiǎn)單的相對(duì)於PE載入點(diǎn)的記憶體偏移。比如,PE載入點(diǎn)為0X400000,那麼代碼節(jié)中的地址0X401000的RVA為(target address) 0x401000 - (load address)0x400000 = (RVA)0x1000。把RVA加上PE的載入點(diǎn)的實(shí)際位址就可以把RVA轉(zhuǎn)化實(shí)際位址。順便說(shuō)一下,按PE的說(shuō)法,記憶體中的實(shí)際位址稱為VA(VIRTUAL ADDRESS).不要忘了早點(diǎn)我說(shuō)的PE的載入點(diǎn)就是HMODULE。 想對(duì)探索記憶體中的任意DLL嗎?用GetModuleHanle(LPCTSTR)取得載入點(diǎn),用你的PE知識(shí)來(lái)幹活吧 5. 資料目錄 PE檔中有很多資料結(jié)構(gòu)需要快速定位。顯然的例子有引入函數(shù),引出函數(shù),資源,重定位。這些東西是以一致的方式來(lái)定位的,這就是資料目錄。 資料目錄是一個(gè)結(jié)構(gòu)陣列,包含16個(gè)結(jié)構(gòu)。每個(gè)元素有一個(gè)定義好的標(biāo)識(shí),如下: // Export Directory #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Import Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Security Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Debug Directory #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Description String #define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // Machine value (MIPS GP) #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_TLS 9 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 typedef struct _IMAGE_DATA_DIRECTORY { ULONG VirtualAddress; ULONG Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; 6. 引入函數(shù) 當(dāng)你使用別的DLL中的代碼或資料,稱為引入。當(dāng)PE載入時(shí),載入器的工作之一就是定位所有引入函數(shù)及資料,使那些位址對(duì)於載入的PE可見。具體細(xì)節(jié)在後面討論,在這裏只是大概講一下。 當(dāng)你用到了一個(gè)DLL中的代碼或資料,你就暗中連接到這個(gè)DLL。但是你不必為“把這些位址變得對(duì)你的代碼有效”做任何事情,載入器為你做這些。方法之一就是顯式連接,這樣你就要確定DLL已被載入,及函數(shù)的位址。調(diào)用LOADLIBARY和GETPROCADDRESS就可以了。 當(dāng)你暗式連接DLL,LOADLIBARY和GETPROCADDRESS同樣還是執(zhí)行了的。只不過(guò)載入器為你做了這些。載入器還保證PE檔所需得任何附加的DLL都已被載入。比如,當(dāng)你連接了KERNEL32.DLL,而它又引入了NTDLL.DLL的函數(shù),又比如當(dāng)你連接了GDI32.DLL,而它又依賴於USER32, ADVAPI32,NTDLL, 和 KERNEL32 DLLs的函數(shù),載入器會(huì)保證這些DLL被載入及函數(shù)的決議。 暗式連接時(shí),決議過(guò)程在PE檔在載入時(shí)就發(fā)生了。如果這時(shí)有什麼問(wèn)題(比如這個(gè)DLL檔找不到),進(jìn)程終止。 VISUAL C++ 6.0 加入了DLL的延遲載入的特徵。它是暗式連接和顯式連接的混合。當(dāng)你延遲載入DLL,連接器做出一些和引入標(biāo)準(zhǔn)規(guī)則DLL類似的東西,但是作業(yè)系統(tǒng)卻不管這些東西,而是在第一次調(diào)用這個(gè)DLL中的函數(shù)的時(shí)候載入(如果還沒(méi)載入),然後調(diào)用GetProcAddress取得函數(shù)的位址。 對(duì)於pe檔要引入的dll都有一個(gè)對(duì)應(yīng)的結(jié)構(gòu)陣列,每個(gè)結(jié)構(gòu)指出這個(gè)dll的名字及指向一個(gè)函數(shù)指標(biāo)陣列的指標(biāo),這個(gè)函數(shù)指標(biāo)陣列就是所謂的IAT(IMORT ADDRESS TABLE)。每個(gè)輸入函數(shù),在IAT中都有一個(gè)保留槽,載入器將在那裏寫入真正的函數(shù)位址。最後特別重要一點(diǎn)的是:模組一旦載入,IAT中包含所要調(diào)用的引入函數(shù)的位址。 把所有輸入函數(shù)放在IAT一個(gè)地方是很有意義的,這樣無(wú)論代碼中多少次調(diào)用一個(gè)引入函數(shù),都是通過(guò)IAT中的一個(gè)函數(shù)指標(biāo)。 讓我們看看是怎樣調(diào)用一個(gè)引入函數(shù)的。有兩種情況需要考慮:有效率的和效率差的。最好的情況像下面這樣: CALL DWORD PTR [0x00405030] 直接調(diào)用[0x405030]中的函數(shù),0x405030位於IAT部分。效率差的方式如下: CALL 0x0040100C ... 0x0040100C: JMP DWORD PTR [0x00405030] 這種情況,CALL把控制權(quán)轉(zhuǎn)到一個(gè)子程式,副程式中的JMP指令跳轉(zhuǎn)到位於IAT中的0x00405030,簡(jiǎn)單說(shuō),它多用了5位元組和JMP多花的時(shí)間。 你可能驚訝引入函數(shù)就採(cǎi)用了這種方式,有個(gè)很好的解釋,編譯器無(wú)法區(qū)別引入函數(shù)的調(diào)用和普通函數(shù)調(diào)用,對(duì)於每個(gè)函數(shù)調(diào)用,編譯器只產(chǎn)生如下指令: CALL XXXXXXXX XXXXXXXX是一個(gè)由連接器填入的RVA。注意,這條指令不是通過(guò)函數(shù)指標(biāo)來(lái)的,而是代碼中的實(shí)際地址。 為了因果的平衡,連接器必須產(chǎn)生一塊代碼來(lái)代替取代XXXXXXXX,簡(jiǎn)單的方法就是象上面所示調(diào)用一個(gè)JMP STUB. 那麼JMP STUB 從那裏來(lái)呢?令人驚異的是,它取自輸入函數(shù)的引入庫(kù)。如果你去察看一個(gè)引入庫(kù),在輸入函數(shù)名字的關(guān)聯(lián)處,你會(huì)發(fā)現(xiàn)與上面JMP STUB相似的指令。 接著,另一個(gè)問(wèn)題就是如何優(yōu)化這種形式,答案是你給編譯器的修飾符,__declspec(import) 修飾符告訴編譯器,這個(gè)函數(shù)來(lái)自另一個(gè)dll,這樣編譯器就會(huì)產(chǎn)生第一種指令。另外,編譯器將給函數(shù)加上__imp_首碼然後送給連接器決議,這樣可以直接把__imp_xxx送到iat,就不需要jmp stub了。 對(duì)於我們這有什麼意義呢,如果你在寫一個(gè)引出函數(shù)的東西並提供一個(gè)頭檔的話,別忘了在函數(shù)前加上修飾符__declspec(import) __declspec(dllimport) void Foo(void); 在winnt.h等系統(tǒng)頭檔中就是這樣做的。 7. PE 檔結(jié)構(gòu) 現(xiàn)在讓我們開始研究PE檔格式,我將從檔的頭部開始,描述每個(gè)PE檔中都有的各種資料結(jié)構(gòu),然後,我將討論更多的專門的資料結(jié)構(gòu)比如引入表和資源,除非特殊說(shuō)明,這些結(jié)構(gòu)都定義在WINNT.H中。 一般地,這些結(jié)構(gòu)都有32和64位之分,如IMAGE_NT_HEADERS32 ,IMAGE_NT_HEADER64等,他們基本上是一樣的,除了64位的擴(kuò)展了某些欄位。通過(guò)#DEFINE WINNT.H都遮罩了這些區(qū)別,選擇那個(gè)資料結(jié)構(gòu)取決於你要如何編譯了(如,是否定義_WIN64) The MS-DOS Header 每個(gè)PE檔是以一個(gè)DOS程式開始的,這讓人想起WINDOWS在沒(méi)有如此可觀的使用者的早期年代。當(dāng)可執(zhí)行檔在非WINDOWS平臺(tái)上運(yùn)行的時(shí)候至少可以顯示出一條資訊表示它需要WINDOWS。 PE檔的開頭是一個(gè)IMAGE_DOS_HEADER結(jié)構(gòu),結(jié)構(gòu)中只有兩個(gè)重要的欄位e_magic and e_lfanew。e_lfanew指出pe file header的偏移,e_magic需要設(shè)定位0x5a4d,被#define 成IMAGE_DOS_SIGNATURE 它的ascii為’MZ’,Mark Zbikowski的首字母,DOS 的原始構(gòu)建者之一。 The IMAGE_NT_HEADERS Header 這個(gè)結(jié)構(gòu)是PE檔的主要定位資訊的所在。它的偏移由IMAGE_DOS_HEADER的e_lfanew給出 確實(shí)有64和32位之分,但我在討論中將不作考慮,他們幾乎沒(méi)有區(qū)別。 typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; 在一個(gè)有效的pe檔裏,Signture被設(shè)為0x00004500,ascii 為’PE00’,#define IMAGE_NT_SIGNTURE 0X00004500;第二個(gè)欄位是一個(gè)IMAGE_FILE_HEADER結(jié)構(gòu),它包含檔的基本資訊,特別重要的是它指出了IMAGE_OPTIONAL_HEADER的大小(重要嗎?);在PE文件中,IMAGE_OPTIONAL_HEADER是非常重要的,但是仍稱作IMAGE_OPTIONAL_HEADER。 IMAGE_OPTIONAL_HEADER結(jié)構(gòu)的末尾就是用來(lái)定位pe檔中重要資訊的位址簿-資料目錄,它的定義如下: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // RVA of the data DWORD Size; // Size of the data }; The Section Table 緊接著IMAGE_NT_HEADERS後的就是節(jié)表,節(jié)表就是IMAGE_SECTION_HEADER的陣列。IMAGE_SECTION_HEADER包含了它所關(guān)聯(lián)的節(jié)的資訊,如位置,長(zhǎng)度,特徵;該陣列的數(shù)目由IMAGE_NT_HEADERS.FileHeader.NumberOfSections指出。具體見下圖 PE中的節(jié)的大小的總和最後是要對(duì)齊的,Visual Studio 6.0中的預(yù)設(shè)值是4k,除非你使用/OPT:NOWIN98 或/ALIGN開關(guān);在.NET中,依然用了默認(rèn)的/OPT:WIN98,但是如果檔小於一特定大小時(shí),就會(huì)採(cǎi)用0X200為對(duì)齊值。 .NET文檔中有關(guān)於對(duì)齊的另一件有趣的事。.NET檔的記憶體對(duì)齊值為8K而不是普通X86平臺(tái)上的4K,這樣就保證了在X86平臺(tái)編譯的程式可以在IA-64平臺(tái)上運(yùn)行。如果記憶體對(duì)齊值為4K,那麼IA-64的載入器就不能載入這個(gè)程式,因?yàn)樗捻?yè)為8K
posted on 2008-07-28 04:04
幽幽 閱讀(692)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
雜集