22. PE结构-PE详解之输入表(导入表)、屠龙刀W32Dasm(静态)、LordPE(动态)工具入门(查找dll、调用函数)
我們知道PE 文件中的數據被載入內存后根據不同頁面屬性被劃分成很多區塊(節),并有區塊表(節表)的數據來描述這些區塊。這里我們需要注意的問題是:一個區塊中的數據僅僅只是由于屬性相同而放在一起,并不一定是同一種用途的內容。例如接著要講的輸入表、輸出表等就有可能和只讀常量一起被放在同一個區塊中,因為他們的屬性都是可讀不可寫的。
其次,由于不同用途的數據有可能被放入同一個區塊中,因此僅僅依靠區塊表是無法確定和定位的。那要怎么辦?對了,PE 文件頭中 IMAGE_OPTIONAL_DEADER32 結構的數據目錄表來指出他們的位置,我們可以由數據目錄表來定位的數據包括輸入表、輸出表、資源、重定位表和TLS等15 種數據。(數據目錄表,不要以為他在前邊出現就不重要哦~)
這節課我們談的是輸入表,為什么需要輸入表呢?因為我們從數據目錄表得到的僅僅是一些指定數據的RVA 和數據塊的尺寸,很明顯,不同的數據塊中的數據組織方式(結構)是顯然不同的,例如輸入表和資源數據塊中的數據就完全是牛馬不相及的兩個東西。因此,我們想要深入了解PE 文件就必須了解這些數據的組織方式,以及了解系統是如何處理調用它們的。
輸入函數
在代碼分析或編程中經常遇到“輸入函數(Import Functions,也稱導入函數)”的概念。這里我們就來解釋下,輸入函數就是被程序調用但其執行代碼又不在程序中的函數,這些函數的代碼位于相關的DLL 文件中,在調用者程序中只保留相關的函數信息(如函數名、DLL 文件名等)就可以。對于磁盤上的PE 文件來說,它無法得知這些輸入函數在內存中的地址,只有當PE 文件被裝入內存后,Windows 加載器才將相關DLL 裝入,并將調用輸入函數的指令和函數實際所處的地址聯系起來。這就是“動態鏈接”的概念。動態鏈接是通過PE 文件中定義的“輸入表”來完成的,輸入表中保存的正是函數名和其駐留的DLL 名等。
實例預演(視頻中將演示,這里只能截圖)
以上是咱這次實驗的小青蛙哈~灰常簡單的一個小程序,如圖雙擊程序只顯示一個對話窗口,然后就結束~試驗用小程序,我們盡量的將內部的結構刪減,調試起來才方便些。我們這次體驗的目的就是想靠所學的知識,試圖來找到MessageBox 在內存中的地址。
注:MessageBox 是來自于USER32.DLL 動態鏈接庫里的一個函數,我們通過對PE 文件的靜態反編譯分析來觀察hello.exe 這個試驗品是如何定位和調用MessageBox 這個在“異鄉”的函數哈。
(MessageBox 有兩個版本,一個是MessageBoxA 還有一個是MessageBoxW 分別帶便ASCII碼形式和UNICODE)
體驗開始:
1. 我們用曾經號稱為屠龍刀的W32DAM 對hello.exe進行反編譯,如圖:
我們可以看到這個程序只有兩個導入模塊(Import Module),分別導入來自兩個動態鏈接庫(USER32.DLL和KERNEL32.DLL)的若干函數,我們還清晰可見,咱要跟蹤的MessageBoxA 就在USER32.DLL 中,這里程序還自動給我們定位了它的虛擬地址:2A2DC,但我們不要用這個,因為我們說過這回我們是來探險的,凡事講究人工……
我們通過W32DASM 的查找功能找出MessageBox 這個函數代碼的位置,并試圖查看他的匯編跳轉~
push xxxx?? push xxxx?? push xxxx?? push xxxx 之后再來一個Call xxxx ……
沒錯,這就是調用函數的一個標準形式。每個push 其實就是將所要調用的函數需要的參數入棧,為啥要入棧呢?這又要從地球的起源說起了……我這里就簡單的說下吧:棧的發明,使得函數和子程序的出現成為可能!對于局部變量和參數,棧的特性最適合不過~不明白不急哈,這個是匯編語言和編譯原理的范疇了,以后咱學習深入自然會碰到,到時候再來詳細解決就可以。
int MessageBox( HWND?hWnd,?LPCTSTR?lpText,?LPCTSTR?lpCaption,?UINT?uType?// style of message box?);
由定義可見MessageBox 函數共有四個參數,因此我們四次push xxxx 分別將參數按照STDCALL 的方式入棧之后,就可以CALL MessageBox 這個函數了。好,既然是CALL 我們的目標函數了,通過反匯編我們就可以觀察到它的地址是:[0042A2AC],難道就這么簡單?42A2AC 就是目標函數的地址?
那我們就直接把程序往下拉,試圖找找這個 42A2AC 的地址吧~
可是…… ……
我們悲劇的發覺,程序壓根還沒到?42A2AC?這地方,到了421FF8 就結束了!!什么情況呢?
或許……或許……或許……在沒詳細講輸入表時我們還不能直接給大家答案……
好吧,這里提問一下,這是一個什么地址呢?偏移地址 or 虛擬地址?
恩,沒錯,這是一個VA 哈,那這個VA 上節課我們說過可以換算成存放在實際物理內存上的方法。具體就是將這個VA 與該程序的各個區塊的VA 地址逐一對比,由于PE 頭文件有記載每個區塊的VA 地址也同時記載著它的實際物理地址,因此我們可以通過判斷該VA 位于哪個區塊內并求出與區塊VA 的差值進而求出該VA 的實際物理地址。那既然前邊直直的路子走不通,我們就試下把他轉換為物理地址試試吧?!
我們看到,咱的42A2AC 地址加載咱的2A000 和 2B000 之間(改程序VC編寫,映像基地址為400000打頭哦~),因此我們可以將該地址定位到改程序位于.idata 區塊內。該區塊的VA 起始地址為42A000,因此42A2AC - 42A000 = 2AC,raw data offs 跟我們說該區塊的物理地址是28000,因此42A2AC 這個VA 所對應的物理偏移地址就是 28000 + 2AC = 282AC。
咱用UE 打開看下282AC 這偏移地址上有啥東西……
282AC 這個地址上存放著 DCA20200 這個數據,翻譯成ASCII 碼也是莫名其妙的說~
但我們把DCA20200 當成一個DWORD 類型的數據來讀的話我們得到數據 0002A2DC(還記得大端與小端吧)
慢著,是不是很熟悉,又是2A****開頭,跟咱之前的地址是不是差不多?那好,我們又按照剛才的方法轉化為偏移地址試試,轉化后得到的偏移地址是:282DC,咱再看看282DC 里邊有啥神秘的東西吧?
哈哈,看到奇跡了嗎?從282DC 地址讀起,ASCII 碼對應的值是MessageBoxA.USER32.dll?
?
輸入表結構
回顧一下,在 PE文件頭的 IMAGE_OPTIONAL_HEADER 結構中的 DataDirectory(數據目錄表) 的第二個成員就是指向輸入表的。而輸入表是以一個 IMAGE_IMPORT_DESCRIPTOR(簡稱IID) 的數組開始。每個被 PE文件鏈接進來的 DLL文件都分別對應一個 IID數組結構。在這個 IID數組中,并沒有指出有多少個項(就是沒有明確指明有多少個鏈接文件),但它最后是以一個全為NULL(0) 的 IID 作為結束的標志。
IMAGE_IMPORT_DESCRIPTOR 結構定義如下:
IMAGE_IMPORT_DESCRIPTOR STRUCT?
? ? union?
? ? ? ? Characteristics????????????? DWORD ? ??
? ? ? ? OriginalFirstThunk??????? DWORD ? ??
? ? ends?
? ? TimeDateStamp???????????????????? DWORD ? ??
? ? ForwarderChain???????????????? ? ? DWORD ? ??
? ? Name????????????????????????????? ? ? ? DWORD ? ??
? ? FirstThunk??????????? ? ? ? ? ? ? ? ? DWORD ? ?
IMAGE_IMPORT_DESCRIPTOR ENDS?
成員介紹:
OriginalFirstThunk
它指向first thunk,IMAGE_THUNK_DATA,該 thunk 擁有 Hint 和 Function name 的地址。
TimeDateStamp
該字段可以忽略。如果那里有綁定的話它包含時間/數據戳(time/data stamp)。如果它是0,就沒有綁定在被導入的DLL中發生。在最近,它被設置為0xFFFFFFFF以表示綁定發生。
ForwarderChain
一般情況下我們也可以忽略該字段。在老版的綁定中,它引用API的第一個forwarder chain(傳遞器鏈表)。它可被設置為0xFFFFFFFF以代表沒有forwarder。
Name
它表示DLL 名稱的相對虛地址(譯注:相對一個用null作為結束符的ASCII字符串的一個RVA,該字符串是該導入DLL文件的名稱,如:KERNEL32.DLL)。
FirstThunk
它包含由IMAGE_THUNK_DATA定義的 first thunk數組的虛地址,通過loader用函數虛地址初始化thunk。在Orignal First Thunk缺席下,它指向first thunk:Hints和The Function names的thunks。
這個OriginalFirstThunk 和?FirstThunk明顯是親家,兩家伙首先名字就差不多。那他們有什么不可告人的秘密呢?來,我們看下面一張圖(畫的很辛苦,大家仔細看哈):
我們看到:OriginalFirstThunk 和?FirstThunk 他們都是兩個類型為IMAGE_THUNK_DATA 的數組,它是一個指針大小的聯合(union)類型。每一個IMAGE_THUNK_DATA 結構定義一個導入函數信息(即指向結構為IMAGE_IMPORT_BY_NAME 的家伙,這家伙稍后再議),然后數組最后以一個內容為0 的 IMAGE_THUNK_DATA 結構作為結束標志。
我們得到?IMAGE_THUNK_DATA 結構的定義如下:
IMAGE_THUNK_DATA STRUC
union u1
? ? ?ForwarderString?????? DWORD? ?? ? ? ? ? ?; 指向一個轉向者字符串的RVA
? ? ?Function????????????????????? DWORD? ???????? ; 被輸入的函數的內存地址
? ? ?Ordinal?????????????????????? DWORD? ?? ? ? ? ?; 被輸入的API 的序數值
? ? ?AddressOfData???????? DWORD? ?? ? ? ? ? ?; 指向 IMAGE_IMPORT_BY_NAME
ends
IMAGE_THUNK_DATA ENDS
我們可以看出由于是union結構,所以IMAGE_THUNK_DATA 事實上是一個雙字大小。該結構在不同時候賦予不同的意義。
那我們怎么來區分何時是何意義呢?
規定如下:
當 IMAGE_THUNK_DATA 值的最高位為 1時,表示函數以序號方式輸入,這時候低 31位被看作一個函數序號。
當?IMAGE_THUNK_DATA 值的最高位為 0時,表示函數以字符串類型的函數名方式輸入,這時雙字的值是一個 RVA,指向一個 IMAGE_IMPORT_BY_NAME 結構。
好,那接著我們討論下指向的這個?IMAGE_IMPORT_BY_NAME 結構。IMAGE_IMPORT_BY_NAME 結構僅僅只有一個字型數據的大小,存有一個輸入函數的相關信息結構。其結構如下:
IMAGE_IMPORT_BY_NAME STRUCT
Hint??????? WORD??? ??Name ? ?? BYTE????? ?
IMAGE_IMPORT_BY_NAME ENDS
結構中的 Hint 字段也表示函數的序號,不過這個字段是可選的,有些編譯器總是將它設置為 0,Name 字段定義了導入函數的名稱字符串,這是一個以 0 為結尾的字符串。
輸入地址表(IAT)
為什么由兩個并行的指針數組同時指向 IMAGE_IMPORT_BY_NAME 結構呢?第一個數組(由 OriginalFirstThunk 所指向)是單獨的一項,而且不能被改寫,我們前邊稱為 INT。第二個數組(由 FirstThunk 所指向)事實上是由 PE 裝載器重寫的。
好了,那么 PE 裝載器的核心操作時如何的呢?這里就給大家揭秘啦~
PE 裝載器首先搜索 OriginalFirstThunk ,找到之后加載程序迭代搜索數組中的每個指針,找到每個 IMAGE_IMPORT_BY_NAME 結構所指向的輸入函數的地址,然后加載器用函數真正入口地址來替代由 FirstThunk 數組中的一個入口,因此我們稱為輸入地址表(IAT)。所以,當我們的 PE 文件裝載內存后準備執行時,剛剛的圖就會轉化為下圖:
此時,輸入表中其他部分就不重要了,程序依靠 IAT 提供的函數地址就可正常運行。
?
實踐
靜態
OriginalFirstThunk? ? INT
輸入表地址為2A000
2A000為虛擬地址,28000為物理地址
?下圖就是導入表的地址。
2A15C-2A000+28000=2815C
2A2DC-2A000+28000=282DC,找到MessageBox函數
FirstThunk? IAT
2A2AC-2A000+28000=282AC
FirstThunk? IAT
?
動態
OriginalFirstThunk? ? INT
用LordPE jump full出動態的exe
win7 64位和win10上lordPE不好使,建議虛擬機XP上運行。
剩下的與靜態分析一樣方法。
注意一點:動態的虛擬地址與物理地址是一樣的。
總結
以上是生活随笔為你收集整理的22. PE结构-PE详解之输入表(导入表)、屠龙刀W32Dasm(静态)、LordPE(动态)工具入门(查找dll、调用函数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 初识空中计算(Over-the-Air
- 下一篇: QT三种窗口、调试终端信息打印、新建菜单