1. 程式人生 > >PE文件結構解析

PE文件結構解析

str 結束 -1 .com 文件頭 .cn cannot signature 初學

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

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_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而存在的

技術分享

PE文件結構解析