PE解析器與加載器編寫指南
最近准備去實習,看公司要求應該開發PE相關的查殺引擎,因此再回頭復習一下PE格式,重新寫一個PE解析器和PE加載器,再此記錄下有關坑。
PE解析器部分:
1)如何確定節區表
正確計算方法: pSection = pNtHeader + sizeof(IMAGE_NT_HEADER) 。
錯誤計算方法:pSection = pFile + sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADER)。
原因:DOS_HEADER后面還留有一段數據,並不緊跟着NT_HEADER,因此你這樣計算是錯誤的!
2)RVA與FOA的轉換
我們現在是PE解析,其都基於FOA,因此我們現在只講解RVA->FOA進行轉換,在后面的PELoader這塊,我們再來分析FOA->RVA。
其分析是基於節表的,循環遍歷節表,判斷其落在相關區間,然后在差值計算,得到對應的FOA,如下圖:
3)導入表的解析
導入表是當DLL加載時,根據該PE文件的導入表對其進行修復操作,之后函數找到該函數就可以找到對應的函數地址。
現在我們來回顧一下導入表結構,其導入表結構如下:
如果我們要獲取整張表的數據,我們需要建立一個二維數組,對應着兩行數據,因此我們設計了如下數據結構:
現在,隨之而來的一個問題:如何確定導入表的個數?PE文件的數據結構似乎沒有一個數據表明其個數。
答案是其連續0為結束,因此我們申請一塊中間內存進行比較即可。(這種方式在PE文件解析中經常用到)。
下面就是初始化導入表的代碼,很好理解:
4)導出表的解析
導出表主要是三張子表,函數名稱表,函數序號表,函數地址表,這三張表用一張圖就很好解決。
因此我們采用如下數據結構存儲:
解析代碼:
先遍歷函數地址表,地址和序號初始化,名字先設置為NULL,之后我們再遍歷名稱和序號表,根據序號將其賦值。
5)重定位的解析
重定位表構造相對簡單,其sizeOfBlock是加上塊頭部8字節的大小,計算偏移是要注意不要再加多了。
重定位元素也很簡單,以WORD為單位,但要注意高4位為0x3才有效,修復重定位表時要檢查該位是否有效。
我們開始是如下的數據結構:
下面為重定位表的解析,其依然采用中間變量的方法:
PE加載器
PE加載器,就是將一個PE文件映射到自己的內存,然后啟動其main函數運行程序。
一個PELoader的實現,需要有幾個注意點:修復IAT表;修復重定位表;將內存屬性寫為可執行。
1)修復IAT表
這里有兩個函數 LoadLibrary 和 GetProcAddress,這兩個函數能幫助我們很好的找到目標函數,其代碼如下:
2)修復重定位表
3)修改屬性並執行Main函數
我們開始將文件映射時,其是只讀屬性,在修復之后如果執行Main函數會觸發0xc0000005頁權限訪問異常。
因此我們需要通過如下代碼將其置為可執行屬性,當然這里存在瑕疵,但是我們只是寫個簡單Demo,也沒太多要求。