# 學習成為人體 PE Parser :::info 筆者最近在閱讀 aaaddress1 的大作: [Windows APT Warfare:惡意程式前線戰術指南](https://www.tenlong.com.tw/products/9789864347544),因為書中圍繞著 PE File 進行,腦容量太小的我又一直忘記 PE 的檔案結構,最後決定還是認真的把它研究一遍並寫成筆記。 ::: PE (Portable Executable) 是一種用於可執行文件、目標文件和動態連結庫的文件格式,主要使用在 32 位和 64 位的 Windows 作業系統上。 > 有點像是 Linux 作業系統中的 elf 檔。 ![](https://i.imgur.com/7FNcEZN.png) ## DOS Header ![](https://i.imgur.com/5tYD4gc.png) 在一串連續的記憶體中, DOS Header 一定會是記憶體中的首段內容, DOS Header 中的幾項資訊會是比較重要的: - e_magic e_magic 幫助我們辨認該 PE 檔案是否合法,一般來說,它應該永遠等於 **MZ** 字串。 如果以 C/C++ 檢查 PE File ,可以這樣做: ```c= #include <windows.h> // ... void parser(char* filePtr){ IMAGE_DOS_HEADER* dosHdr = (IMAGE_DOS_HEADER *)filePtr; if(dosHdr->e_magic != IMAGE_DOS_SIGNATURE){ return; } // ... } // ... ``` - e_lfanew 觀察 e_lfanew 之前,必須先了解什麼是 RVA (Relative Virtual Address), RVA 是程式入口點的參考位址,舉例來說: 如果程式被放入虛擬地址(Virtual address, VA)的 `0x01000000` 處,且 RVA 位於 `0x102D6C` 處,那麼程式在記憶體中的實際入口就會是 **VA + RVA**: ``` 0x01000000 + 0x00102D6C = 0x01102D6C ``` e_lfanew 其實就是指向了 NT Headers 的 RVA ,換個角度思考,我們將前面的 dosHdr 加上偏移量 (在這邊指 RVA),就可以獲得 NT Headers 的起始位址: ```c= IMAGE_NT_HEADERS* ntHdrs = (IMAGE_NT_HEADERS *)((size_t)dosHdr + dosHdr->e_lfanew); ``` ## NT Headers 透過讀取 DOS Header 獲得 NT Headers 的起始位址以後,我們就可以對 PE 檔案做更進一步的檢驗。 NT Headers 共包含了兩大結構,分別是 File Header 以及 Optional Header 。 ### File Header ![](https://i.imgur.com/1hVgEzv.png) 參考上圖,在 File Header 結構中有多個屬性,每個屬性代表的資訊如下: - Machine 紀錄 PE 檔案所存放的機械碼屬於哪一種指令集架構: - x86 - ARM - x64 - NumberOfSections 一個 PE File 通常會有好幾段塊狀區域, NumberofSections 紀錄了 PE 檔案的區段數量。 > 這個參數對我們撰寫程式解析 PE File 非常有幫助,至於那些塊狀區段存了什麼,晚點會提到。 - TimeDateStamp 紀錄程式編譯時間的時間戳。 - PointerToSymbolTable 符號表地址,用於除錯,一般為 0 。 - NumberOfSymbols 如果符號表存在,這邊會記錄符號數量。 - Characteristics 紀錄了整個 PE 的屬性,包含: - Executable - Info of redirection - 32-bit or not - DLL modules ### Optional Header ![](https://www.researchgate.net/profile/Mohamed-Belaoued/publication/312032405/figure/fig2/AS:446360207007745@1483431974413/Structure-of-the-PE-optional-header-and-location-of-the-IAT.png) > [Source](https://www.researchgate.net/figure/Structure-of-the-PE-optional-header-and-location-of-the-IAT_fig2_312032405) Optional Header 的中文稱**可選段**,實際上,如果要讓 PE 能夠順利地被執行程式裝載器使用, Optional Header 為必備的。 > **補充**: > Optional Header 不存在於 Object File (COFF),而是在編譯的連結階段才會由連結器補上。 參考上圖, Optional Header 包含了很多參數,下面針對重要的參數作介紹: - Address of entry point 程式碼編譯後,程式的入口點,也就代表當 Program 被作業系統載入時, Process 會從這邊開始執行。 > 一般來說,入口點會指向 .text section 的函式開頭。 - ImageBase 記錄了 PE 檔案 mapping 到記憶體上的預設位址,通常為 `0x400000` 或是 `0x800000` 。 - SizeOfImage 記錄了當程式處於動態執行階段需要多少空間才能存放整個 Image 。 - Section alignment 動態的區域對齊, 32-bit 的環境下預設大小為 0x1000 bytes 。 - File alignment 靜態的區域對齊, 32-bit 的環境下預設大小為 0x200 bytes 。 > 假設有不足 0x200 bytes 的資料要放進塊狀區段,塊狀區段的大小為 0x200 bytes ,如果資料多於預設大小,塊狀區段的大小則為 0x400 bytes 。 - Size of headers DOS Header + NT Headers + Section Headers 的大小。 - Data directory - Export table - Import table - Ressource table - Exception table - Import Address table ## Section Headers Section Headers 的位址緊隨在 NT Headers 的後方,使用 C/C++ 可以輕鬆的獲得其位址: ```c= IMAGE_SECTION_HEADER* sectHdr = (IMAGE_SECTION_HEADER *)((size_t)ntHdrs + sizeof(*ntHdrs)); ``` 至於 Section Headers 的本體到底是什麼呢?它其實就是一個存放塊狀區段資訊的陣列: ```c= for (size_t i = 0; i < ntHdrs->FileHeader.NumberOfSections; i++){ printf("\t#%.2x - %8s - %.8x - %.8x \n", i, sectHdr[i].Name, sectHdr[i].PointerToRawData, sectHdr[i].SizeOfRawData); } ``` 每一塊存放塊狀區段資訊的空間都會有以下屬性: - PointerToRawData 該區段處存在靜態檔案的偏移量。 - SizeofRawData 該區段的實際大小。 - VirtualAddress 相較於映像基址的相對偏移量。 - VirtualSize 顯示該區段需要被分配多少動態空間。 - Characteristics 紀錄該區段是否可讀、可寫、可執行。 ## 常見區段 - .text 用於存放程式碼。 - .data 用來宣告已初始化的資料與常數。 - .bss 存放已宣告但尚未初始化的變數。 - .rdata 存放唯讀資料。 - .idata 存放引入的函式與資料,這些資料會在 Process 建立時,由執行程式裝載器負責填充。 - .edata 存放用來導出給其他程式使用的函式與資料。 - .rsrc 用於記錄程式使用了哪些資源。 - reloc 重定位,當 PE 程式載入失敗,會以此段作為參考進行調整。 ## 總結 了解 PE File 的基本結構後,我們就可以將~~惡意~~ shellcode 添加至目標檔案再將 Entry point 指向惡意程式區段的 Virtual Address 做些壞壞的事(?) ![](https://i.imgur.com/3sljwva.png) 本文章介紹的內容大概只有 Windows APT Warfare:惡意程式前線戰術指南整本書的皮毛,如果想學更多就去下單買一本[ Windows APT Warfare:惡意程式前線戰術指南](https://www.tenlong.com.tw/products/9789864347544)吧! ## References - [Portable Executable](https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%A7%BB%E6%A4%8D%E5%8F%AF%E6%89%A7%E8%A1%8C) - [Windows PE 檔案結構及其載入機制](https://www.itread01.com/content/1545708077.html) - [Windows-APT-Warfare Repo](https://github.com/aaaddress1/Windows-APT-Warfare/blob/main/source/chapter%2302/peParser/peParser.cpp) - [Windows PE文件各个节(Section)分析](https://blog.csdn.net/weixin_38164023/article/details/106665175)