Windows安全之PE結構(五)之導入表(IAT)結構分析

輸入表結構

回顧一下, 在PE文件頭的IMAGE_OPTIONAL_HEADER 結構中的DataDirectory(數據目錄表)的第二個成員就是指向輸入表的, 而輸入表是以一個IMAGE_IMPORT_DESCRIPTOR(簡稱IID)的數組開始. 每個PE文件鏈接進來的DLL文件都分別對應一個IID數組結構在這個IID數組中,並沒有指出有多少個項(就是沒有
IMAGE_IMPORT_DESCRIPTOR STRUCT
UNION
Characteristics DWORD ?
OriginalFirstThunk DWORD ? ;一個指針 指向INT基址 (IMAGE_THUNK_DATA數組)
ends
TimeDataStamp DWORD ?
ForwarderChain DWORD ?
Name  DWORD ?
FirstThunk DWORD ?  ;一個指針 指向IAT基址 (IMAGE_THUNK_DATA數組)
IMAGE_IMPORT_DESCRIPTOR ENDS 


以下IMAGE_THUNK_DATA這個結構十分重要 WINDOW能把靜態和動態的內存分配達至最小的變動因為UNION是共享內存的
最高位為1時 表示函數以序號方式導入,低31位被看作為一個函數序號
最高位為0時 表示函數以字符串類型的函數名方式導入,這時雙字的值是一個RVA,指向一個IMAGE_IMPORT_BY_NAME的結構中。
IMAGE_THUNK_DATA STRUCT
union u1
ForwarderString DWORD ? ;指向一個轉向者字符串的相對地址(RVA)
Function DWORD ? ;被輸入的函數的內存地址
Ordinal  DWORD ? ;被輸入的API函數值
AddressOfData DWORD ? ;指向IMAGE_IMPORT_BY_NAME 
ends
IMAGE_THUNK_DATA ENDS


OriginalFirstThunk 和 FirstThunk (IAT/INT)在硬盤靜態時都是指向同一個地方 就是IMAGE_IMPORT_BY_NAME
IMAGE_IMPORT_BY_NAME STRUCT
Hint WORD ? ;表示函數的序號
Name BYTE ? ;Name 字段定義了函數的名稱字符串, 以0作為結尾
END STRUCT  

圖1:







可是為什麼會有兩個數組中的元素 也指向同一個結構呢?? 而且是相對應的如:數組[0] 數組2[0] 也是指向同一個序號為0 的函數

這就是牽涉到 PE加載器的 核心操作

整個流程是這樣的:

PE加載器首先搜索到OriginalFirstThunk , 找到之後加載程序搜索數組中的每個指針,找到每個IMAGE_IMPORT_BY_NAME 結構所指向的輸入,然後加載器用函數真正入口地址來替代由FirstThunk數組的一個入口,因此我們稱為IAT,導入函數表

文件被加載到內存後 FirstThunk指向的是一個存放地址的數組,可以這樣理解。加載前他和INT是一樣 ,
INT是不可以修改的 IAT是可以修改的
如下圖 把kernel32.dll 作為一例子








以下一個靜態調試的例子;

首先我們尋找應該是路徑為
1.IID位置
2.尋找 OriginalFirstThunk 和 FirstThunk 的位置
3.由此尋找他們指向的地方存放的值是多少
4.如果成功 應該會查看到IMAGE_IMPORT_BY_NAME的 一個序號加上一堆ASCII碼的

首先我們由上一章講的例子 作一個簡單的分析
用LordPE打開helloworld.exe, 查看一下導入表的位置







然後用之前學的算法 查看一下這個導入表在硬盤物理地址中是什麼地址, 首先定位所在區塊及計算偏移值














例子中18000(IID的虛擬地址)-18000(區塊首地址) = 0 , 把0 + 5A00 就是首地址了 不清楚可以看回之前章節


打開UltraEdit 查看一下0x5A00
圖片:








這個就是IID的首地址 然後一直查下去就是整個IID的結構體
紅線的兩個是OriginalFirstThunk 和 FirstThunk
記錄一下:
OriginalFirstThunk 指向: 0x000181B8
FirstThunk 指向: 0x00018350

這兩個也是虛擬地址吧,要是尋找物理中的地址 只好又算一下
OriginalFirstThunk 在硬盤中的物理偏移 : 5A00 + 0x1B8(181B8-18000) = 5BB8
FirstThunk 在硬盤中的物理偏移 : 5A00 + 0x350(18350-18000) = 5D50


把物理基址(ROffset) + 偏移就是這兩個的值 , 接著往這兩個地方查找一下









神奇地 存放著兩個一樣的值 : 指向0x00018380 這個就是第一個IMAGE_THUNK_DATA 的虛擬地址
再一次要在硬盤中尋址 只好換成物理地址 5A00 + ( 18380 - 18000) = 5D80






如上圖去看看 就是message的名稱存放位置,也就是IMAGE_IMPORT_BY_NAME的位置, 01E3就是序號 然後就是一個字符串
不過以上是靜態分析, 引證我們一開始的理論,在硬盤存放時, 兩個Thunk所指向的位置所存放的值也是指向同一個IMAGE_IMPORT_BY_NAME. 實際上thunk也就是一個dword類型而已



以下是動態分析的例子


大致步驟是一樣, 不過我們如果獲取加載後的內存內容呢,
我們可以用LordPE 透過dump full拷貝所有內存到新的exe去
然後直接用LordPE 去打開那個dump的exe
如圖:






重覆一下上面的步驟,查一下FirstThunk所指向的還是不是IMAGE_IMPORT_BY_NAME

1.查看一下IID的位置,LordPE import table的基地址,這裡為18000H




2.定位所在區塊,算出偏移, 這裡是.idata區塊 import table所在的RVA是18000h , idata的基址RVA也是18000h , 相減是零, 0 + 18000h(ROffset) 就是dump程序中的硬盤物理地址







3.打開UltraEdit 查一下0x18000 位置 , 這裡可以看到是一樣的 跟靜態分析時 只是地址有點改變 因為內存映射的關係 一定會有點不同, 可是所指向的是一個相對虛擬地址 所以都跟靜態的一樣, 供運行的時侯使用




注意:由於這裡時直接從加載內存後的內容直接拷背過來, 所以直接使用0x000181B8 和 0x00018350 是可以的, 這裡看的都是虛擬地址(加載內存後) , 可跟真實有點不同的是加載地址為0x00400000H 而在ultraedit中開啟的加載地址由0x00000000h 開始 有點不一樣 可以相對偏移(RVA)還是可用的


4.直接去看這兩個地址的內容 , 發現跟靜態時有點不一樣了, 靜態時兩個值都時一樣存放

IMAGE_IMPORT_BY_NAME的地址, 可是這理不是以最高位不是0了 他是0x77D66534h 這個是一個函數地址

這個就是導入表導入的第一個函數









查看一下地址0X000181B8(OriginalThunk指向的位置)中的存放的值是0X00018380這個是IMAGE_IMPORT_BY_NAME結構的地址(由於最高位是0,我們可以知道他一定是指向IMAGE_IMPORT_BY_NAME)




Comments

Popular posts from this blog

Android Kernel Development - Kernel compilation and Hello World

How does Nested-Virtualization works?

Understanding ACPI and Device Tree