在上篇:PE手工分析-PE頭 中我們了解了PE文件頭內容,在此基礎上我們來分析一下導入表和導入函數地址表的內容.
還是使用上篇使用的PE文件來分析,上一篇中我們基本上已經定位出PE頭的位置以及相應內容.
在PE擴展頭中包含 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//數據目錄表
通過該數據目錄表我們可以進一步對導入表和導入函數地址表進行詳細的分析(其數據目錄表分析也雷同)
詳細信息可以查看MSDN定義.
數據目錄表結構定義:
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]數據目錄表分析如下:
索引 |
數據目錄表 |
文件偏移地址 |
偏移 |
大小 |
說明 |
0 |
導出表 |
00000170h |
00 00 00 00 |
00 00 00 00 |
沒有導出 |
1 |
導入表 |
00000178h |
00 E0 01 00 |
64 00 00 00 |
|
|
... |
|
|
|
|
12 |
導入地址表 |
000001d0h |
A0 E2 01 00 |
3C 02 00 00 |
|
|
… |
|
|
|
|
|
... |
|
|
|
|
|
... |
|
|
|
|
導入表RVA=01E000
IAT RVA=01E2A0
RVA的概念請參考:RVA,另外可以參考《加密解密ii》中的第二章介紹
了解了RVA之后我們就明白了在獲取導入表的RVA之后需要根據節地址轉換成文件相對地址FOA
所以為了獲取導入表的文件相對地址需要對PE的節表進行分析
首先需要知道節表所在的位置(緊跟數據目錄表結尾)
總共16個數據目錄,導入地址函數表在第12(0開始)個位置
節表起始位置=1d0+8+3×8=1F0
節表結構如下:
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME];// #define IMAGE_SIZEOF_SHORT_NAME 8 union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
從上面鏡像頭分析可以之后該PE中包含有7個節
每個節包含40個字節
總共占有空間:40×7=280(0x118)->01F0-20E
可以看到每個節對應的內容如下:
段名稱 |
虛擬地址 |
虛擬大小 |
物理地址 |
物理大小 |
標志 |
PointerToRawData |
.textbss |
1000 |
0 |
10000 |
0 |
2EE0 |
|
.text |
11000 |
|
897D |
00008A00 |
60000020 |
00000400 |
.rdata |
1A000 |
|
24B5 |
2600 |
40000040 |
8E00 |
.data |
01D000 |
|
05F4 |
2000 |
C0000040 |
B400 |
.idata |
0001E000 |
|
10C8 |
1200 |
C0000040 |
B600 |
.rsrc |
00020000 |
|
0459 |
0600 |
40000040 |
C800 |
.reloc |
00021000 |
|
06DA |
0800 |
42000040 |
CE00 |
節表的分析完畢,然后我們需要針對導入表的RVA獲取相應的FOA
RVA:01E000=>PointerToRawData:B600
指向導入表描述符
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // DWORD OriginalFirstThunk; // } DUMMYUNIONNAME; //0001e270=>B600+270=b870->01E4DC->_foo@4 DWORD TimeDateStamp; // 0 DWORD ForwarderChain; //0 DWORD Name; //01E4E6=>B600+4E6=BAE6->dllExport.dll DWORD FirstThunk; //01E4AC=>B600+4AC=BAAC->01E4DC->_foo@4 } IMAGE_IMPORT_DESCRIPTOR;
其文件內容如下:
OriginalFirstThunk指向地址(RVA)01E4DC->(FOA)BADC
FirstThunk指向地址(RVA)01E4AC->(FOA)BAAC對應結構為
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32;
相應IMAGE_THUNK_DATA指向的內容為
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; //00 BYTE Name[1]; //_foo@4 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
這里對應的導入表分析基本完成了
- 相應的IAT分析如下:
RVA:01E2A0=>PointerToRawData:B600+(2A0)=B8A0在找到了IAT表的地址后讓我們來看一下其中的內容
可以看到這里的BAAC正好處理B8A0的區域中.
所以很顯然,FirstThunk屬於IAT中的某一個地址區域
相應PE文件內容如下
Dll加載之前導入表結構
在DLL加載之后導入表中內容將被操作系統填充為函數的VA
到這里基本上已經把IAT和INT綁定起來了,而在函數調用過程中將如何實現調用IAT的函數地址呢?
程序中每個調用 API 函數的 CALL 指令所使用的地址都是相應函數登記在 IAT 表的地址
源文檔 <http://blog.csdn.net/misterliwei/article/details/840983>
那么,IAT導入函數地址表,和導入表有什么聯系呢??
其實,所有的DLL的IMAGE_IMPORT_DESCRIPTOR結構的FirstThunk指向的是一片連續的內存空間,第一個DLL的IMAGE_IMPORT_DESCRIPTOR結構的FirstThunk的值,就是IAT表的起始地址!
也就是說,導入表中的首個IMAGE_IMPORT_DESCRIPTOR的FirstThunk字段,在內存中,等同於IMAGE_NT_HEADER.OptionHeader.DataDirectory[12].VirtualAddress
數據目錄表第13項,索引值為12,就是IAT了。
在RING3的API劫持中,很多人都會選擇使用IAT劫持,也就是基於這個理論的。
源文檔 <http://tieba.baidu.com/f?kz=726947835>
對於IAT和導入表之間的關系用如下圖片作為結尾
導入表中的FirstThunk指向IAT表中的某一項
之前的理解有問題所以后面加以修改