PE知識復習之PE的導入表
一丶簡介
上一講講解了導出表. 也就是一個PE文件給別人使用的時候.導出的函數 函數的地址 函數名稱 序號 等等.
一個進程是一組PE文件構成的. PE文件需要依賴那些模塊.以及依賴這些模塊中的那些函數.這個就是導入表需要做的.
確定PE依賴那個模塊. 確定PE依賴的那個函數. 以及確定函數地址.
總共分為三部分講解.
導入表定位位置: 在擴展頭中有一個數據目錄結構體. 第二項保存的就是導入表的 RVA 以及大小.
如下圖所示:
EXE文件.沒有導出表.有一個導入表. RVA 是 0x1A1C0 位於節Text中. 虛擬地址位 0x11000 文件偏移為 0x400
轉換為 FOA = 1A1C0 - 11000 + 400 = 0x95c0
我們發現在文件中定位導入表的時候都是0,原因是程序加載到內存中.需要用到的時候.操作系統才會往這個地方填寫數據.
二丶導入表結構
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) 指向IAT結構注釋表明了 } DUMMYUNIONNAME; DWORD TimeDateStamp; // 時間戳. // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders DWORD Name; //指向DLL名字的 RVA DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
導入表大小為十進制的20個字節. 16進制的 0x14. 如果以16進制為一行. 則是 一行零4個字節
導入表跟導出表不同.導出表只有一個.里面有子表記錄. 而導入表你依賴一個模塊.則有一個導入表存在.
導入表結束位置是20個字節的連續為0的數據為結束位置. 也就是導入表最后一項都為0的時候.說明導入表結束了.
對於導入表來說.我們只需要關心三個成員.上面都標紅了.
會一一進行講解.首先從最簡單的成員開始.
2.1 Name成員. 確定依賴的模塊的名字是什么
我們說過.一個PE文件.依賴模塊. 那么這個成員就是記錄了.我要依賴的模塊的名字是什么.是一個RVA屬性. RVA指向了一個ASCII碼字符串.以0結尾.
因為在文件中導入表並沒有.所以我們直接在內存中查看.
根據數據目錄 導入表位置 0x1A1C0 + ImageBase(0x400000) == 0x41AC0
在內存中的0x41AC0位置.則是導入表的位置. 我們看一下.
導入表大小總共一行零4個字節. 倒數第二個成員則是 Name的 RVA 0x1A4A6
我們可以加上ImageBase 去內存中查看.
可以通過RVA 屬性.看到導入表依賴的模塊名字就是 VCRUNTIME140D.dll 帶有D結尾的.dll說明是調試DLL. 140是編譯器版本.說明是
VS2015編譯的 .VCRuntime 是運行庫 . 說明我們這個程序是一個 Debug版本編譯的程序. 並且使用編譯器 140版本編譯的.
我們查看的這個Name屬性.描述的就是 VCRUNTIME140D.dll 這個模塊的信息了.如果想看其它依賴的模塊就需要查看下一張導入表.
下一張導入表在第一章導入表的下面.最后一項的導入表全部為0. 我們下一張導入表的 依賴模塊的模塊名稱的 RVA 屬性是 0x1A75A
VA = Imagebase + RVA = 41A75A
依次查看即可.
2.2 確定依賴的函數的名稱
上面我們講了Name成員.確定了導入表依賴的DLL的名字.那么我們導入表怎么確定依賴了那些函數那?
這個主要講解導入表的第一個成員跟最后一個成員.
如下圖所示:
第一個成員指向了一個INT 表.最后一個成員指向了一個 IAT表.
INT :: 導入名稱表 Improt Name Table
IAT:: 導入地址表 Improt Address Table
Name成員直接指向一個 ASC 結尾的字符串.
根據上圖所示. 兩張表是一樣的. 但是所在位置是不一樣的名字也不一樣.一個叫做 INT 一個叫做IAT
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
結構體大小: 4個字節. 他是一個聯合體.找最大的.
里面有4個成員.為當前的4個字節起了四個名字. 真正有用的是下面兩個. 也就是說有的時候需要用第三個成員.
有的時候需要用第四個成員. 而第四個成員是指向一個 IMAGE_IMPORT_BY_NAME的結構的RVA
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; //編譯器決定,不是空的話,就是函數在導出表中的 函數地址表的導出索引. CHAR Name[1]; //函數名稱,0結尾. } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
無論是第一個成員還是最后一個成員.都能確定 我一來的當前模塊的那個函數.
為什么需要兩個表. 這個下面會將. 首先講解的就是無論使用那個表.都能找到依賴當前模塊的函數.
第一個成員找:
INT表 INT表是4個字節.最后0結尾. INT表有多大.就是說依賴這個模塊的多少個函數.
IAT 同上. 0結尾.
那么我們怎么去尋找?
看這個表的4個字節. 最高位為1那么就是函數的導出序號. 去掉最高位.就是函數的序號. 也就是說我們看的是序號.
如果最高位不是1,那么找的就是一個 RVA ,一個指向 IMAGE_IMPROT_BY_NAME的結構.
例如下圖:
INT 或者 IAT表. 都可以通過最高位判斷. 是函數的序號.還是函數的名字.
INT或者IAT就是兩種情況, 高位為1, 那么去掉高位就是依賴的函數序號. 不是1, 那么就是一個RVA. 指向了一個 IMAGE_IMPROT_BY_NAME 結構.
以一個導入表為例
INT的 RVA 為 1A2A8 VA = 41A2A8
41A2A8是INT表開始. 每一個是4個字節,以0結尾. 觀看第一項. 高位為0,所以 0x1A48E 是一個RVA. 一個指向 IMAGE_IMPROT_BY_NAME 的結構
VA = 41A48E
高位兩個字節,是函數在導出表中的導出索引. 后面就是以0結尾的函數名稱了.
總結來說: 不管是INT表還是 IAT表. 主要看其高位值,高位為1,那么去掉高位,就是函數的序號. 高位為0.指向一個結構.這個結構保存了函數的導出序號.以及函數名稱.
在IMAGE_IMPROT_BY_NAME 結構中的 HINT 如果不是空,那么這個序號(索引) 就是導出表的函數地址表的索引. 我們可以直接拿着這個索引去導出表中獲取函數地址.
2.3 確定函數地址
如果我們使用DLL的函數.那么在程序中.調用這個DLL的函數.那么就會生成一個間接Call
比如我們程序調用MessageBoxA
反匯編
跳轉過去之后.會看到內存中有一個地址
這個地址才是真正的MessageBox的地址
在我們導入表中,最后一個成員 IAT表.就是上面所說的表,保存了函數地址表.
那么這和我們說的結構是不一樣的. IAT不是說跟INT是一樣的嗎?
PE加載前加載后的區別.
一樣是一樣的.但是需要分清 PE加載前.還有PE加載后.如果加載前,那么IAT跟INT一樣.都可以找到依賴的函數名稱.
如果是加載后.也就是在內存中的話.那么IAT表保存的就是函數的地址.
PE加載后如下圖:
IAT表保存的就是函數地址了.
從導入表中找到IAT表.
IAT表的RVA 偏移為 0x1A098 VA == 41A098
IAT表中存儲了函數地址,4個字節為單位.0x6AD79CF0 就是函數 __Vcrt_loadlibraryExW . INT表中存儲的就是 依賴的函數名稱.上面我們也看到了.
三丶知識總結
導入表大小為20個字節. 十六進制 0x14 ,一行零4個字節.
1.導入表重要成員有三個. INT表. Name表. IAT表.
PE加載前.
INT 表 IAT表相同. 根據INT或者IAT表的高位,高位為1.去掉高位就是函數序號. 高位為0. 那么是一個RVA偏移. 指向函數名稱表.
函數名稱表
HINT 當前函數在導出函數地址表中的索引
Name 當前函數的名稱.
PE加載后INT 表同上. IAT表變成了存儲函數地址的地址表了.
2. Name 民稱表. 直接指向DLL名稱文件名. 是一個RVA .注意是直接指向.
3.INT IAT表.的RVA 都是定位INT IAT表位置. 定位的位置是INT IAT表.這個表存儲的才是數據