0x00 前言 PE文件是portable File Format(可移植文件)的簡寫,我們比較熟悉的DLL和exe文件都是PE文件,。了解PE文件格式有助于加深對操作系統(tǒng)的理解,,掌握可執(zhí)行文件的數(shù)據(jù)結(jié)構(gòu)機(jī)器運(yùn)行機(jī)制,對于逆向破解,,加殼等安全方面方面的同學(xué)極其重要,。接下來我將通過接下來幾篇詳細(xì)介紹PE文件的格式。 0x01 基本概念 PE文件使用的是一個(gè)平面地址空間,,所有代碼和數(shù)據(jù)都被合并在一起,,組成一個(gè)很大的組織結(jié)構(gòu)。文件的內(nèi)容分割為不同的區(qū)塊(Setion,又稱區(qū)段,,節(jié)等),,區(qū)段中包含代碼數(shù)據(jù),各個(gè)區(qū)塊按照頁邊界來對齊,,區(qū)塊沒有限制大小,,是一個(gè)連續(xù)的結(jié)構(gòu)。每塊都有他自己在內(nèi)存中的屬性,,比如:這個(gè)塊是否可讀可寫,,或者只讀等等。 認(rèn)識(shí)PE文件不是作為單一內(nèi)存映射文件被裝入內(nèi)存是很重要的,,windows加載器(PE加載器)便利PE文件并決定文件的哪個(gè)部分被映射,,這種映射方式是將文件較高的偏移位置映射到較高的內(nèi)存地址中。當(dāng)磁盤的數(shù)據(jù)結(jié)構(gòu)中尋找一些內(nèi)容,,那么幾乎能在被裝入到內(nèi)存映射文件中找到相同的信息,。但是數(shù)據(jù)之間的位置可能改變,其某項(xiàng)的偏移地址可能區(qū)別于原始的偏移位置,,不管怎么樣,所表現(xiàn)出來的信息都允許從磁盤文件到內(nèi)存偏移的轉(zhuǎn)換,,如下圖: PS:PE文件頭以下的地址無論在內(nèi)存映射中還是在磁盤映射中都是一樣的,,當(dāng)內(nèi)存分頁和磁盤分頁一致時(shí)無需進(jìn)行地址轉(zhuǎn)換,只有當(dāng)磁盤分頁和內(nèi)存分頁不一樣時(shí)才要進(jìn)行地址轉(zhuǎn)化,,這點(diǎn)很重要,,拿到PE文件是首先查看分頁是否一致。前兩天一直沒碰到內(nèi)存和磁盤分頁不一樣的,,所以這個(gè)點(diǎn)一直沒發(fā)現(xiàn),,今天特來補(bǔ)上。 下面要介紹幾個(gè)重要概念,,分別是基地址(ImageBase),相對虛擬地址(Relative Virtual Address),,文件偏移地址(File Offset)。 1)基地址 定義:當(dāng)PE文件通過Windows加載器被裝入內(nèi)存后,,內(nèi)存中的版本被稱作模塊(Module),。映射文件的起始地址被稱作模塊句柄(hMoudule),可以通過模塊句柄訪問其他的數(shù)據(jù)結(jié)構(gòu),。這個(gè)初始內(nèi)存弟子就是基地址,。 內(nèi)存中的模塊代表著進(jìn)程從這個(gè)可執(zhí)行文件中所需要的代碼,數(shù)據(jù),,資源,,輸入表,輸出表以及其他有用的數(shù)據(jù)結(jié)構(gòu)所使用的內(nèi)存都放在一個(gè)連續(xù)的內(nèi)存塊中,編程人員只要知道裝載程序文件映像到內(nèi)存的基地址即可,。在32位系統(tǒng)中可以直接調(diào)用GetModuleHandle以取得指向DLL的指針,,通過指針訪問DLL module的內(nèi)容,例如: HMODULE GetmoduleHandle(LPCTSRT lpModuleName),; 當(dāng)調(diào)用該函數(shù)時(shí),,傳遞一個(gè)可執(zhí)行文件或者DLL文件名字字符串。如果系統(tǒng)找到該文件,,則返回該可執(zhí)行文件的或者DLL文件映像加載到的基地址,。也可以調(diào)用GetModuleHandle,傳遞NULL參數(shù),則返回調(diào)用的可執(zhí)行文件的基地址,。 2)相對虛擬地址 在可執(zhí)行文件中,,有相當(dāng)多的地方需要指定內(nèi)存的地址。例如:引用全局變量時(shí),,需要指定它的地址,。PE文件盡管有一個(gè)首選的載入地址(基地址),但是他們可以載入到進(jìn)程空間的任意地方,所以不能依賴與PE的載入點(diǎn),。由于這個(gè)原因,,必須有一個(gè)方法來指定一個(gè)地址而不是依賴于PE載入點(diǎn)。 為了在PE文件中避免有確定的內(nèi)存地址,,出現(xiàn)了相對虛擬地址(Relative Virtual Addres,簡稱RVA)的概念,。RVA只是內(nèi)存中的一個(gè)簡單的相對于PE文件裝入地址的偏移地址,它是一個(gè)“相對”地址,,或者稱位“偏移量”地址,。例如:假設(shè)一個(gè)EXE文件從地址40000h處載入,并且它的代碼區(qū)塊開始于4010000h,,代碼區(qū)的RVA將是: 目標(biāo)地址401000h ——轉(zhuǎn)入地址400000h則RVA=1000h,。 將RVA地址轉(zhuǎn)換成真實(shí)地址,只需簡單的翻轉(zhuǎn)這個(gè)過程:將實(shí)際裝入地址加上RVA即可得到實(shí)際的內(nèi)存地址,。順便一提,,在PE用語里,實(shí)際的內(nèi)存地址被稱作虛擬地址(Vritual Address,簡稱VA),另外也可以把虛擬地址想象為加上首選裝入地址的RVA,。不要忘了前面提到的裝入地址等同于模塊句柄,,它們之間的關(guān)系如下: 虛擬地址(VA)=基地址(ImageBase)+相對虛擬地址(RVA) 3)文件偏移地址 當(dāng)PE文件存儲(chǔ)在磁盤上時(shí),某個(gè)數(shù)據(jù)的位置相對于文件頭的偏移量也稱文件偏移地址(FileOffset)或者物理地址(RAW Offset)。文件偏移地址從PE文件的第一個(gè)字節(jié)開始計(jì)數(shù),起始為零。用十六進(jìn)制工具比如:winhex,,hexworkshop都可以查看,。注意這個(gè)物理地址和虛擬地址的區(qū)別,物理地址是文件在磁盤上相對于文件頭的地址,,而虛擬地址是PE可執(zhí)行程序加載在內(nèi)存中的地址,。 0x02 幾個(gè)重要頭部信息介紹 接下來介紹MS-DOS頭部信息,PE文件頭信息及幾個(gè)重要字段,。 1)MS-DOS頭部 每個(gè)PE文件是以一個(gè)DOS程序開始的,,有了它,一旦程序在DOS下執(zhí)行,,DOS就能辨別出這是個(gè)有效的執(zhí)行體,,然后運(yùn)行緊隨MZ header(后面會(huì)介紹)之后的DOS stub(DOS塊)。DOS stub實(shí)際上是一個(gè)有效的EXE,,在不支持PE文件格式的操作系統(tǒng)中,,它將簡單顯示一個(gè)錯(cuò)誤提示,類似于字符串“This Program cannot be run in MS-DOS”,。用戶通常對DOS stub 不感興趣,,因?yàn)榇蠖鄶?shù)情況下他們由匯編器自動(dòng)生成。平常把DOS stub和DOS MZ頭部合稱為DOS文件頭,。 PE文件的第一個(gè)字節(jié)起始于一個(gè)傳統(tǒng)的MS-DOS頭部,,被稱作IMAGE_DOS_HEADER。其IMAGE_DOS_HEADER的結(jié)構(gòu)如下(左邊的數(shù)字是到文件頭的偏移量): IMAGE_DOS_HEADER STRUCT { +0h WORD e_magic // Magic DOS signature MZ(4Dh 5Ah) DOS可執(zhí)行文件標(biāo)記 +2h WORD e_cblp // Bytes on last page of file +4h WORD e_cp // Pages in file +6h WORD e_crlc // Relocations +8h WORD e_cparhdr // Size of header in paragraphs +0ah WORD e_minalloc // Minimun extra paragraphs needs +0ch WORD e_maxalloc // Maximun extra paragraphs needs +0eh WORD e_ss // intial(relative)SS value DOS代碼的初始化堆棧SS +10h WORD e_sp // intial SP value DOS代碼的初始化堆棧指針SP +12h WORD e_csum // Checksum +14h WORD e_ip // intial IP value DOS代碼的初始化指令入口[指針I(yè)P] +16h WORD e_cs // intial(relative)CS value DOS代碼的初始堆棧入口 +18h WORD e_lfarlc // File Address of relocation table +1ah WORD e_ovno // Overlay number +1ch WORD e_res[4] // Reserved words +24h WORD e_oemid // OEM identifier(for e_oeminfo) +26h WORD e_oeminfo // OEM information;e_oemid specific +29h WORD e_res2[10] // Reserved words +3ch DWORD e_lfanew // Offset to start of PE header 指向PE文件頭 } IMAGE_DOS_HEADER ENDS 這個(gè)結(jié)構(gòu)中有兩字段很重要,,一個(gè)是e_magic,一個(gè)是e_lfanew,。e_magic(一個(gè)字大小)字段需要被設(shè)置為5A4Dh這個(gè)也是PE程序載入的重要標(biāo)志,,這個(gè)值非常有意思,他們對應(yīng)的字符分別位Z和M,,是為了紀(jì)念MS-DOS的最初創(chuàng)建者Mark Zbikowski而專門設(shè)置的,,由于在hex編輯器中顯示是由低位到高位故顯示為4D5Ah,剛好是創(chuàng)建者的名字縮寫。另一個(gè)字段是e_lfanew,。這個(gè)字段表示的是真正的PE文件頭部相對偏移地址(RVA),,它指出了真正PE頭部文件偏移位置。它占用四個(gè)字節(jié),,位于文件開始偏移的3ch字節(jié)中,。 下面我將用hexworkshop打開一個(gè)pe文件向大家展示一下上面這段話的含義。
第一張圖說明的就是IMAGE_DOS_HEADER的第一個(gè)字段e_magic的值與地址,。第二張圖就是上面所講的第二個(gè)關(guān)鍵字段e_fannew字段的值(注意:不同的PE程序這個(gè)值可能不一樣,,但原理一樣),這個(gè)值就是PE頭文件的起始偏移量,。 2)PE文件頭文件 相對于MS-DOS頭文件,,PE頭文件PEheader要復(fù)雜的多,,下面將詳細(xì)講解其中的幾個(gè)字段。 緊跟著DOS頭文件下面的就是peheader,。PEheader是PE相關(guān)結(jié)構(gòu)NT映像頭(IMAGE_NT_HEADER)的簡稱,,其中包含許多PE裝載器用到的重要字段。執(zhí)行體在支持PE文件結(jié)構(gòu)的操作系統(tǒng)執(zhí)行時(shí),,PE裝載器將IMAGE_DOS_HEADER結(jié)構(gòu)中的e_fanew字段找到PEheader的起始偏移量,,加上基址得到PE文件頭的指針: PNTHeader=IMAGBase+dosHeader->e_lfanewr(其實(shí)就是去字段e_lfanew的值)。 下面來討論IMAGE_NT_HEADER的結(jié)構(gòu),,它是由三個(gè)字段組成(左邊的數(shù)字是PE文件頭的偏移量):IMAGE_NT_HEADER STRUCT { +0h Signature DWORD //PE文件標(biāo)志 +4h FileHeader IMAGE_FILE_HEADER //文件頭初始偏移地址 +18 optionalHeader IMAGE_OPTION_HEADER //另一個(gè)重要頭部初始偏移地址 } IMAGE_NT_HEADER ENDS 下面對這三個(gè)字段逐個(gè)詳細(xì)分析:
這個(gè)字段是PE文件的標(biāo)志字段,,通常設(shè)置成00004550h,其ASCII碼為PE00,,這個(gè)字段是PE文件頭的開始,,前面的DOS_HEADER結(jié)構(gòu)中的字段e_lfanew字段就是指向這里。 2.IMAGE_FILE_HEADER字段 這個(gè)字段也是包含幾個(gè)字段結(jié)構(gòu),,它包含了PE文件的一些基本信息,,最重要的是其中一個(gè)域指出了IMAGE_OPTIONAL_HEADER的大小。 typedef struct _IMAGE_FILE_HEADER { WORD Machine;//運(yùn)行平臺(tái) WORD NumberOfSections;//文件的區(qū)塊數(shù)目 DWORD TimeDateStamp;//文件創(chuàng)建的用時(shí)間戳標(biāo)識(shí)的日期 DWORD PointerToSymbolTable;//指向符號(hào)表(用于調(diào)試) DWORD NumberOfSymbols;//符號(hào)表中符號(hào)的個(gè)數(shù) WORD SizeOfOptionalHeader;//IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)大小 WORD Characteristics;//文件屬性 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; 上圖標(biāo)出七個(gè)字段的位置及各自的值,。 1)Machine字段,,表示目標(biāo)CPU 的類型。 幾個(gè)常見的及其標(biāo)識(shí)如下: 機(jī)器 標(biāo)識(shí) Intel I386 14ch MIPS R3000 162h Alpha AXP 184h Power PC 1F0h MIPS R4000 184h 根據(jù)以上信息我們知道這個(gè)PE文件要運(yùn)行在Intel I386機(jī)器上,。 2)NumberOfSection,,標(biāo)識(shí)區(qū)塊的數(shù)目,關(guān)于區(qū)塊后面會(huì)詳細(xì)講,。 3)TimeDateStamp 這個(gè)字段沒啥好說的,,指的就是PE文件創(chuàng)建的事件,這個(gè)時(shí)間是指從1970年1月1日到創(chuàng)建該文件的所有的秒數(shù),。 4)PointerToSymbolTable,。這個(gè)字段用的比較少,略 5)NumberOfSymbol,。這個(gè)字段也用得很少,,略 6)SizeOfOptionalHeader:緊跟著IMAGE_FILE_HEADER后面的數(shù)據(jù)大小,這也是一個(gè)數(shù)據(jù)結(jié)構(gòu),,它叫做IMAGE_OPTIONAL_HEADER,其大小依賴于是64位還是32位文件,。32位文件值通常是00EOh,對于64位值通常為00F0h,。 7)Characteristics:文件屬性,,普通EXE文件這個(gè)字段值為010fh,DLL文件這個(gè)字段一般是0210h,。 |
|