PE文件結構解析


說明:本文件中各種文件頭格式截圖基本都來自看雪的《加密與解密》;本文相當《加密與解密》的閱讀筆記。

 

1.PE文件總體結構

PE文件框架結構,就是exe文件的排版結構。也就是說我們以十六進制打開一個.exe文件,開頭的那些內容就是DOS頭內容,下來是PE頭內容,依次類推。

如果能認識到這樣的內含,那么“exe開頭的內容是不是就直接是我們編寫的代碼”(不是,開頭是DOS頭內容)以及“我們編寫的代碼被編排到了exe文件的哪里”(在.text段,.text具體地址由其相應的IMAGE_SECTION_HRADER指出)此類的問題答案就顯而易見了。

exe文件從磁盤加載到內存,各部份的先后順序是保持不變的,但由於磁盤(一般200H)和內存(一般1000H)區塊的對齊大小不一樣,所以同一內容在磁盤和在內存中的地址是不一樣的。

換言之你在磁盤上看到一段內容一內容要到在內存中找到它--假設它是能映射到內容的部份--那么要做相應的地址轉換。(比如你在Ultraedit中看到某幾個字節而想在OllyDbg中找到這幾個字節那么需要進行地址轉換)

另外要注意,PE文件中存放的地址值都是內存中的地址,這些地址在OllyDbg中不需要轉換到其指定的位置就能找到其指向的內容;這要根據這個地址找到內容在Ultraedit的地址,需要將此RVA址轉換成文件偏移地址。

還要注意DOS頭/PE頭/塊表,映射到內存時屬同一區塊而且是第一區塊,所以此三者上的RVA和文件偏移地址是相等的。

 

 2.DOS頭部

2.1MS-DOS頭部(IMAGE_DOS_HEADER)

最后的e_lfanew即是PE文件的RVA地址

我們在前邊已經提過,對於DOS頭/PE頭/區塊表三部分RVA和文件偏移地址是相等的,所以上邊在十六進制文本編緝器中,直接轉向e_lfanew指向的000000B0可以正好找到PE頭。

2.2DOS stub

DOS stub是當操作系統不支持PE文件時執行的部分,一般由編譯器自己生成內容是輸出“This program cannot be run in MS-DOS mode”等提示。

PE文件頭的位置由e_lfanew指出而不是在固定位置,所以DOS stub允許你改成自己想要執行的代碼,想寫多長寫多長;但一般直接不理會。

 

3.PE頭部

3.1 PE Signature

四個字節,內容“PE\0\0”,對應十六進制“50 45 00 00”

 

3.2 IMAGE_FILE_HEADER

SizeOfOptionalHeader指了OptionalHeader的大小,NumberOfSections指出了文件的區塊數;沒有指向OptionalHeader和區塊表的指針,這暗示區塊表緊接在OpthionalHeader后,OpthonalHeader緊接在FileHeader扣,緊接的意思是沒有空格的。

 

3.3 IMAGE_OPTIONAL_HEADER

其中ImageBase指出程序裝載的基地址

 

4.區塊表

4.1 IMAGE_NT_HEADER

其中VirtualAddress指出了區塊進入內存后的RVA地址(OD找區塊用這個地址)PointerToRawDATA指出區塊在磁盤文件中的地址(十六進制編緝器找區塊用這個地址)

 

4.2 文件偏移地址和相對偏移地址的換算

1. 各區塊自身不論多大,其自身差值都不會愛影響

2. 但如果其大小大於200h那么會影響下一區塊的差值:比如當.text大小大於200h那么.rdata的文件偏移量將會后移;如果.text大小大於1000h那么.rdata的RVA也會后移

3.也就說表10-7中的差值只是說一般是這樣子,但當程序很大時各區塊的差值還是得重新計算;當然無論怎么樣總是有:差值=區塊RVA-區塊文件偏移地址

 

5.數據目錄表

數據目錄表是IMAGE_OPTIONAL_HEADER結構的最后一個成員,類型為IMAGE_DATA_DIRECTORY * 16

數據目錄表各成員位置由VirtualAddress指出,並且不是像PE頭接在DOS頭后不遠處一樣,一般都在很遠的地址;所以一般都是跳過先講完區塊然后再回頭講,也因此初學者可能會感到有些混亂。

 

5.1輸出表

數據目錄表的第一個成員指向輸出表的RVA,指向的地址是IMAGE_EXPORT_DIRECTORY結構。

其中AddressofFunctions指向輸出函數的地址數組,AddressOfNames指向輸出函數的名稱數組,AddressOfNameOrdinale指向輸出函數的輸出序數數組。

AddressOfNames和AddressOfNameOrdinale的順序是相同的,也就是說AddressOfNameOrdinale第一個元素的值就是就是AddressOfNames第一個函數的輸出序數,依次類推。

使用輸出序數做為數組下標到AddressofFunctions指向的地址數組即可找到函數對應的地址。PE重寫IAT時使用GetProcAddress通過函數名獲取函數地址基本也就是這個流程。

 

5.2輸入表

數據目錄表的第二個成員指向輸入表的RVA;指向的地址是IMAGE_IMPORT_DESCRIPTOR(IID)結構,一個IID對應一個DLL,最后以一個全0的IID表示結束

 OriginalFristThunk和FirstThunk都指向IMAGE_THUNK_DATA結構;IMAGE_THUNK_DATA都指向同一個IMAGE_IMPORT_BY_NAME

 最重要的還是要理解為什么需要INT和IAT兩個東西指向同一個東西;其流程是這樣:

1.INT是不可寫的,IAT是PE加載器可重寫的

2.在編譯的時候,編譯器不懂IAT要填什么,就隨便填成了和INT一樣的內容(所以用十六進制編緝器查看時IAT和INT的內容是一樣的)

3.PE裝載器加載時根據INT找到IMAGE_IMPORT_BY_NAME中的函數名,然后使用GetProcAddress(HMODULE hModule,LPCSTR lpProcName)找到函數對應的地址(hModule是DLL的句柄,lpProcName是IMAGE_IMPORT_BY_NAME中的函數名)

4.PE裝載器使用查找到地址重寫IAT(所以用OllyDbg查看時IAT和INT的內容是不一樣的)

5.所以可以直接這樣理解:IAT初始是什吐槽內容並不要緊、INT就是為了重寫IAT而存在的

 

5.3資源

windows系統中的各種可視元素叫做資源,包括快捷鍵(Accelerator)、位圖(Bitmap)、光標(Cursor)、對話框(Dialog Box)、圖標(Icon)、菜單(Menu)、字符串表(String Table)、工具欄(Toolbar)、版本信息(Version Information)等。

數據目錄表的第三個成員指向資源結構的RVA,資源結構一般是相同的三層IMAGE_RESOURCE_DIRECTORY+n * IMAGE_RESOURCE_DIRECTORY_ENTRY加上指向最終資源代碼的IMAGE_RESOURCE_DATA_ENTRY構成。

其中NumberOfNameEntries的值加上NumberOfIdEntries的值等於緊接在IMAGE_RESOURCE_DIRECTORY后邊的IMAGE_RESOURCE_DIRECTORY_ENTRY的個數。

根據IMAGE_RESOURCE_DIRECTORY_ENTRY所在層級的不同,Name和OffsetToData的含義不相同。

Name:

不管在哪層,當最高位為1時低位值做指針使用;當最高位為0時低位值做編號使用。

更具體的,一般在第一層時高位為0低位用做編號表示資源類型比如是對話框還是菜單;第二高位為1低位為指向IMAGE_RESOURCE_DIR_STRING_U的指針該結構保存資源的名稱;第三層時高位為0低位用做編號表示該資源中的語言比如是英語還是漢語。

OffsetToData:

不管在哪層,當最高位為1時指向下一層目錄塊的起始地址;當最高位為0時指向IMAGE_RESOURCE_DATA_ENTRY。

更具體的,一般在第一和第二層時最高位為1,指向下一層目錄塊的起始地址;在第三層時最高位為0,指向IMAGE_RESOURCE_DATA_ENTRY。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM