說明:本文件中各類文件頭格式截圖基本都來自看雪的《加密與解密》;本文至關《加密與解密》的閱讀筆記。windows
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和文件偏移地址是相等的。操作系統
最後的e_lfanew便是PE文件的RVA地址3d
咱們在前邊已經提過,對於DOS頭/PE頭/區塊表三部分RVA和文件偏移地址是相等的,因此上邊在十六進制文本編緝器中,直接轉向e_lfanew指向的000000B0能夠正好找到PE頭。指針
2.2DOS stuborm
DOS stub是當操做系統不支持PE文件時執行的部分,通常由編譯器本身生成內容是輸出「This program cannot be run in MS-DOS mode」等提示。
PE文件頭的位置由e_lfanew指出而不是在固定位置,因此DOS stub容許你改爲本身想要執行的代碼,想寫多長寫多長;但通常直接不理會。
四個字節,內容「PE\0\0」,對應十六進制「50 45 00 00」
SizeOfOptionalHeader指了OptionalHeader的大小,NumberOfSections指出了文件的區塊數;沒有指向OptionalHeader和區塊表的指針,這暗示區塊表緊接在OpthionalHeader後,OpthonalHeader緊接在FileHeader扣,緊接的意思是沒有空格的。
其中ImageBase指出程序裝載的基地址
其中VirtualAddress指出了區塊進入內存後的RVA地址(OD找區塊用這個地址)PointerToRawDATA指出區塊在磁盤文件中的地址(十六進制編緝器找區塊用這個地址)
1. 各區塊自身不論多大,其自身差值都不會愛影響
2. 但若是其大小大於200h那麼會影響下一區塊的差值:好比當.text大小大於200h那麼.rdata的文件偏移量將會後移;若是.text大小大於1000h那麼.rdata的RVA也會後移
3.也就說表10-7中的差值只是說通常是這樣子,但當程序很大時各區塊的差值仍是得從新計算;固然不管怎麼樣老是有:差值=區塊RVA-區塊文件偏移地址
數據目錄表是IMAGE_OPTIONAL_HEADER結構的最後一個成員,類型爲IMAGE_DATA_DIRECTORY * 16
數據目錄表各成員位置由VirtualAddress指出,而且不是像PE頭接在DOS頭後不遠處同樣,通常都在很遠的地址;因此通常都是跳過先講完區塊而後再回頭講,也所以初學者可能會感到有些混亂。
數據目錄表的第一個成員指向輸出表的RVA,指向的地址是IMAGE_EXPORT_DIRECTORY結構。
其中AddressofFunctions指向輸出函數的地址數組,AddressOfNames指向輸出函數的名稱數組,AddressOfNameOrdinale指向輸出函數的輸出序數數組。
AddressOfNames和AddressOfNameOrdinale的順序是相同的,也就是說AddressOfNameOrdinale第一個元素的值就是就是AddressOfNames第一個函數的輸出序數,依次類推。
使用輸出序數作爲數組下標到AddressofFunctions指向的地址數組便可找到函數對應的地址。PE重寫IAT時使用GetProcAddress經過函數名獲取函數地址基本也就是這個流程。
數據目錄表的第二個成員指向輸入表的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而存在的
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。