本來是想先把Shellcode Loader給更新了的,但是涉及到一些PE相關的知識,所以就先把PE給更了,后面再把Shellcode Loader 給補上。
聲明:本文章內容來自于B站小甲魚
1.PE的結構
首先我們要講一個PE文件,就得知道它的結構,可以參考下面的這張照片
2.DOS頭部
首先一個PE文件,就是它的DOS結構。 其中,需要我們去了解的,就是一開始的那個DOS標記以及我們執行的PE文件頭
那么我們隨便打開一個EXE來看一下。我門可以看見標志性的MZ
然后我們直接定位到3C這個位置,這里指向PE文件的頭(寫死的)??
可以看見在這個PE文件中,它的PE開始是在80這個位置
于是我們就去到80這個位置
3.IMAGE_NT_HEADERS
1.Signature
這個字段,是用來標志著這是一個真正的PE文件,從上面,我們跳到了這里,就能看見我們的Signature標識字段。
2.IMAGE_FILE_HEADER
這個結構如下
其中重要的,就是SizeOfOptionalHeader這個值,這個就決定了后面的IMAGE_OPTIONAL_HEADER32的大小
3.IMAGE_OPTIONAL_HEADER32
其中一些比較重要的結構
- AddressOfEntryPoint 程序執行入口RVA? 在偏移值為28的地方?
我們去那個偏移位置找一找(相對于PE頭的偏移)DWORD類型 ,指向的是我們的14C
- ImageBase
- SectionAlignment和FileAlignment字段
其中FileAlignment默認是200,我們自己去PE文件找找是不是這么回事(它的偏移地址是3C)
而且他是DWORD類型,四個字節,我們在對應的地方確實是能看到是200!!?
- DataDirectory(數組)
其實可以這樣子理解? 他定義了一個長度為16的數組,然后數組的每一個元素都是一個結構,這個結構里面都存儲了兩個元素,一個就是數據的起始RVA,另外一個就是數據塊的長度
數組中的有些元素十分的重要(導入表,導出表,資源,重定位表,IAT表)
當我們需要尋找特定的資源的時候,我們就可以去找到這個數組的第三個元素,獲取到對應的RVA以及長度,當我們要查看PE文件導入了哪些DLL的API的時候,我們就可以去導入表里面獲取RVA(Relative Virtual Address)和長度!!!!??
4.區塊
接下來就是塊表,首先他是這么一個結構
然后,我們來看name,像這種以.開頭的(當然這個.不是必須的)
接下來就是相應的一些結構,比較重要一點的就是PointerToRawData 和 SizeOfRawData ,通過這兩個,我們就能找到下一個塊表的位置
除此,還有一個比較重要,這個標定了該區塊的屬性!!!
最后的判斷,是通過存在的屬性進行OR的操作判斷的,比如我們看到它的characteristic是6000000的話,我們就知道是通過20(Code) OR 20000000(Execute) OR 40000000(Read) == (60000020)? 這樣就能判斷這個對應的屬性
對于區塊,一般都有以下的類型
然后就是區塊的對對齊
RVA
相對虛擬地址
目標RVA和文件偏移的計算
在處理PE文件的時候,任何的RVA必須經過文件的偏移的換算,才能用來定位并訪問文件中的數據。
- 首先,我們要循環掃描每個區塊在內存中的起始RVA,并且根據區塊的大小,算出區塊的結束RVA,判斷目標的RVA是否落在該區塊內
- 然后通過獲取到了目標區塊之后,用目標RVA減去起始的RVA,這樣就得到了目標RVA相對于起始地址的偏移量RVA2
- 最后,根據該區塊表在文件中所處的偏移地址,將這個值加上RV2,就得到了文件的偏移地址!!!
假設我們有一個虛擬地址666666,那么我們就要去區塊中查找
通過定位,我們可以發現他在.reloc這個區塊中
那么RVA2 = 666666 - 66000 = 666,然后我們看到這個區塊在文件中的偏移地址在60E00
所以我們這個虛擬地址在物理內存中的地址就是60E00 + 666 = 61466?
5.導入表
說到導入表,我們肯定不會陌生,我們在一開始的PE頭中,就講過一個IMAGE_OPTIONAL_HEADER里面有一個特別重要的DataDirectory這個地方,里面放著我們的導入表!!!!
其中的第二個成員就是導入表,如果我們跳轉到它的RVA之后,我們就能看見一個以IMAGE_IMPORT_DESCRIPTOR(簡稱IID)的數組開始的,每一個被加載進來的DLL文件都分別對應一個IID數組結構,當我們看到一個IID全為0的時候,就代表結束!!!!
對于每一個IID,它的結構如下:
它的長度為5個DWORD,其中重要的兩個就是我們的FirstTrunk 和 OriginalFirstTrunk
6.IAT &&? IMAGE_IMPORT_BY_NAME
終于,我們學免殺要學的IAT表終于要出現了!!!!? 不過在此之前,我們先來看一張圖片
其中能看見IID的第一個DWORD OriginalFirstTrunk指向了INT表的IMAGE_THUNK_DATA
然后DWORD FirstThunk 指向了IAT表中的IMAGE_THUNK_DATA ,并且這兩個都指向了IMAGE_IMPORT_BY_NAME這個表
其中,對于我們的IMAGE_THUNK_DATA
然后就是IMAGE_IMPORT_BY_NAME
IAT(Import Address Table)導入地址表
那么下面,我們就來舉個栗子演示一下:
首先我們來找一個程序,直接看他的DataDirectory中的導入表的地址
然后定位到塊中的 .idata中
我們可以追蹤到之后發現它存在兩個IID,即調用了兩個動態連接庫
并且我們去查看第一個IID的Original_First_Thunk,指向的是INT表中的IMAGE_THUNK_DATA,并且這個值是2A15C,但是這是一個RVA,所以我們要找到它的實際偏移,我們用這個RVA減去.idata塊的RVA 得到的RVA2再加上Raw Data offs 就是我們的實際偏移地址,2815C
然后又找到一個02A2DC ,我們在上面說過,如果開頭是0的話,它的值是RVA,指向一個IMAGE_IMPORT_BY_NAME,所以,我們就還要去找282DC這個地方
這樣,我們就找到了我們的真正的函數的地址(在此期間指針指向了兩次),來看這么一個圖(第一個盒子里面是ORIGINAL_FIRST_THUNK一開始寫錯了)
那么剛才我們講了通過OriginalFirstThunk找,那么現在我們通過FirstThunk查找
首先還是找到iid中的FirstThunk,然后轉換為實際偏移地址就是282AC
然后我們就去282AC,找到指向的實際偏移地址是282DC
然后再去282DC處找,發現282dc處也能找到我們的這個函數的真實位置
過程如下
然后我們去讓這個程序運行起來,并且將他的內存dump出來,然后再去跟蹤它的導入表
這時候我們再去跟蹤IAT表,282AC這個部分。終于,我們就找到了這個函數的真正的地址
這時候,我們的IAT表就是這樣的了(在內存中,IAT表不在需要通過名字索引,而是通過地址了)
然后本次的PE文件就到這了,當然了PE文件遠不止這些,導入表,重定位表,資源等等,當日后在免殺中用到的時候,我們再相應進行更新。
下一篇Blog,我們就會更新對應的Shellcode Loader了!!!!