PE教程6: Import Table(引入表
PE教程6: Import Table(引入表)
本課我們將學習引入表。先警告一下,對於不熟悉引入表的讀者來說,這是一堂又長又難的課,所以需要多讀幾遍,最好再開啟偵錯程式來好好分析相關結構。各位,努力啊!
下載範例。
理論:
首先,您得了解什麼是引入函式。一個引入函式是被某模組呼叫的但又不在呼叫者模組中的函式,因而命名為"import(引入)"。引入函式實際位於一個或者更多的DLL裡。呼叫者模組裡只保留一些函式資訊,包括函式名及其駐留的DLL名。現在,我們怎樣才能找到PE檔案中儲存的資訊呢? 轉到 data directory 尋求答案吧。再回顧一把,下面就是 PE header:
IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER <>
IMAGE_NT_HEADERS ENDS
optional header 最後一個成員就是 data directory(資料目錄):
IMAGE_OPTIONAL_HEADER32
STRUCT
....
LoaderFlags dd ?
NumberOfRvaAndSizes dd ?
DataDirectory IMAGE_DATA_DIRECTORY
16 dup(<>)
IMAGE_OPTIONAL_HEADER32 ENDS
data directory 是一個 IMAGE_DATA_DIRECTORY 結構陣列,共有16個成員。如果您還記得節表可以看作是PE檔案各節的根目錄的話,也可以認為 data directory 是儲存在這些節裡的邏輯元素的根目錄。明確點,data directory 包含了PE檔案中各重要資料結構的位置和尺寸資訊。 每個成員包含了一個重要資料結構的資訊。
Member | Info inside |
---|---|
0 | Export symbols |
1 | Import symbols |
2 | Resources |
3 | Exception |
4 | Security |
5 | Base relocation |
6 | Debug |
7 | Copyright string |
8 | Unknown |
9 | Thread local storage (TLS) |
10 | Load configuration |
11 | Bound Import |
12 | Import Address Table |
13 | Delay Import |
14 | COM descriptor |
上面那些金色顯示的是我熟悉的。瞭解 data directory 包含域後,我們可以仔細研究它們了。data directory 的每個成員都是 IMAGE_DATA_DIRECTORY 結構型別的,其定義如下所示:
IMAGE_DATA_DIRECTORY
STRUCT
VirtualAddress dd ?
isize dd ?
IMAGE_DATA_DIRECTORY
ENDS
VirtualAddress
實際上是資料結構的相對虛擬地址(RVA)。比如,如果該結構是關於import
symbols的,該域就包含指向IMAGE_IMPORT_DESCRIPTOR
陣列的RVA。
isize 含有VirtualAddress所指向資料結構的位元組數。
下面就是如何找尋PE檔案中重要資料結構的一般方法:
- 從 DOS header 定位到 PE header
- 從 optional header 讀取 data directory 的地址。
- IMAGE_DATA_DIRECTORY 結構尺寸乘上找尋結構的索引號: 比如您要找尋import symbols的位置資訊,必須用IMAGE_DATA_DIRECTORY 結構尺寸(8 bytes)乘上1(import symbols在data directory中的索引號)。
- 將上面的結果加上data directory地址,我們就得到包含所查詢資料結構資訊的 IMAGE_DATA_DIRECTORY 結構項。
現在我們開始真正討論引入表了。data directory陣列第二項的VirtualAddress包含引入表地址。引入表實際上是一個 IMAGE_IMPORT_DESCRIPTOR 結構陣列。每個結構包含PE檔案引入函式的一個相關DLL的資訊。比如,如果該PE檔案從10個不同的DLL中引入函式,那麼這個陣列就有10個成員。該陣列以一個全0的成員結尾。下面詳細研究結構組成:
IMAGE_IMPORT_DESCRIPTOR
STRUCT
union
Characteristics dd ?
OriginalFirstThunk dd ?
ends
TimeDateStamp
dd ?
ForwarderChain dd ?
Name1 dd ?
FirstThunk
dd ?
IMAGE_IMPORT_DESCRIPTOR ENDS
結構第一項是一個union子結構。
事實上,這個union子結構只是給
OriginalFirstThunk
增添了個別名,您也可以稱其為"Characteristics"。
該成員項含有指向一個 IMAGE_THUNK_DATA
結構陣列的RVA。
什麼是 IMAGE_THUNK_DATA?
這是一個dword型別的集合。通常我們將其解釋為指向一個
IMAGE_IMPORT_BY_NAME
結構的指標。注意 IMAGE_THUNK_DATA
包含了指向一個 IMAGE_IMPORT_BY_NAME
結構的指標:
而不是結構本身。
請看這裡:
現有幾個 IMAGE_IMPORT_BY_NAME
結構,我們收集起這些結構的RVA
(IMAGE_THUNK_DATAs)組成一個陣列,並以0結尾,然後再將陣列的RVA放入
OriginalFirstThunk。
此 IMAGE_IMPORT_BY_NAME
結構存有一個引入函式的相關資訊。再來研究 IMAGE_IMPORT_BY_NAME
結構到底是什麼樣子的呢:
IMAGE_IMPORT_BY_NAME
STRUCT
Hint dw ?
Name1 db ?
IMAGE_IMPORT_BY_NAME
ENDS
Hint
指示本函式在其所駐留DLL的引出表中的索引號。該域被PE裝載器用來在DLL的引出表裡快速查詢函式。該值不是必須的,一些聯結器將此值設為0。
Name1 含有引入函式的函式名。函式名是一個ASCIIZ字串。注意這裡雖然將Name1的大小定義成位元組,其實它是可變尺寸域,只不過我們沒有更好方法來表示結構中的可變尺寸域。The
structure is provided so that you can refer to the data structure with descriptive
names.
TimeDateStamp 和 ForwarderChain 可是高階東東: 讓我們精通其他成員後再來討論它們吧。
Name1 含有指向DLL名字的RVA,即指向DLL名字的指標,也是一個ASCIIZ字串。
FirstThunk
與 OriginalFirstThunk
非常相似,它也包含指向一個 IMAGE_THUNK_DATA
結構陣列的RVA(當然這是另外一個IMAGE_THUNK_DATA
結構陣列)。
好了,如果您還在犯糊塗,就朝這邊看過來:
現在有幾個 IMAGE_IMPORT_BY_NAME
結構,同時您又建立了兩個結構陣列,並同樣寸入指向那些 IMAGE_IMPORT_BY_NAME
結構的RVAs,這樣兩個陣列就包含相同數值了(可謂相當精確的複製啊)。
最後您決定將第一個陣列的RVA賦給
OriginalFirstThunk,第二個陣列的RVA賦給
FirstThunk,這樣一切都很清楚了。
OriginalFirstThunk | IMAGE_IMPORT_BY_NAME | FirstThunk | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| | | | |||||||||||||||||||||||||||||||||
|
|
|
|
|
現在您應該明白我的意思。不要被IMAGE_THUNK_DATA這個名字弄糊塗: 它僅是指向 IMAGE_IMPORT_BY_NAME 結構的RVA。 如果將 IMAGE_THUNK_DATA 字眼想象成RVA,就更容易明白了。OriginalFirstThunk 和 FirstThunk 所指向的這兩個陣列大小取決於PE檔案從DLL中引入函式的數目。比如,如果PE檔案從kernel32.dll中引入10個函式,那麼IMAGE_IMPORT_DESCRIPTOR 結構的 Name1域包含指向字串"kernel32.dll"的RVA,同時每個IMAGE_THUNK_DATA 陣列有10個元素。
下一個問題是: 為什麼我們需要兩個完全相同的陣列? 為了回答該問題,我們需要了解當PE檔案被裝載到記憶體時,PE裝載器將查詢IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 這些結構陣列,以此決定引入函式的地址。然後用引入函式真實地址來替代由FirstThunk指向的 IMAGE_THUNK_DATA 陣列裡的元素值。因此當PE檔案準備執行時,上圖已轉換成:
OriginalFirstThunk | IMAGE_IMPORT_BY_NAME | FirstThunk | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| | | | |||||||||||||||||||||||||||||||||
|
|
| |
|
由OriginalFirstThunk
指向的RVA陣列始終不會改變,所以若還反過頭來查詢引入函式名,PE裝載器還能找尋到。
當然再簡單的事物都有其複雜的一面。有些情況下一些函式僅由序數引出,也就是說您不能用函式名來呼叫它們:
您只能用它們的位置來呼叫。此時,呼叫者模組中就不存在該函式的 IMAGE_IMPORT_BY_NAME
結構。不同的,對應該函式的 IMAGE_THUNK_DATA
值的低位字指示函式序數,而最高二進位 (MSB)設為1。例如,如果一個函式只由序數引出且其序數是1234h,那麼對應該函式的
IMAGE_THUNK_DATA
值是80001234h。Microsoft提供了一個方便的常量來測試dword值的MSB位,就是
IMAGE_ORDINAL_FLAG32,其值為80000000h。
假設我們要列出某個PE檔案的所有引入函式,可以照著下面步驟走:
- 校驗檔案是否是有效的PE。
- 從 DOS header 定位到 PE header。
- 獲取位於 OptionalHeader 資料目錄地址。
- 轉至資料目錄的第二個成員提取其VirtualAddress值。
- 利用上值定位第一個 IMAGE_IMPORT_DESCRIPTOR 結構。
- 檢查 OriginalFirstThunk值。若不為0,順著 OriginalFirstThunk 裡的RVA值轉入那個RVA陣列。若 OriginalFirstThunk 為0,就改用FirstThunk值。有些聯結器生成PE檔案時會置OriginalFirstThunk值為0,這應該算是個bug。不過為了安全起見,我們還是檢查 OriginalFirstThunk值先。
- 對於每個陣列元素,我們比對元素值是否等於IMAGE_ORDINAL_FLAG32。如果該元素值的最高二進位為1, 那麼函式是由序數引入的,可以從該值的低位元組提取序數。
- 如果元素值的最高二進位為0,就可將該值作為RVA轉入 IMAGE_IMPORT_BY_NAME 陣列,跳過 Hint 就是函式名字了。
- 再跳至下一個陣列元素提取函式名一直到陣列底部(它以null結尾)。現在我們已遍歷完一個DLL的引入函式,接下去處理下一個DLL。
- 即跳轉到下一個 IMAGE_IMPORT_DESCRIPTOR 並處理之,如此這般迴圈直到陣列見底。(IMAGE_IMPORT_DESCRIPTOR 陣列以一個全0域元素結尾)。
示例:
本例程開啟一PE檔案,將所有引入函式名讀入一編輯控制元件,同時顯示 IMAGE_IMPORT_DESCRIPTOR 結構各域值。
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
IDD_MAINDLG equ 101
IDC_EDIT equ 1000
IDM_OPEN equ 40001
IDM_EXIT equ 40003
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
ShowImportFunctions
proto :DWORD
ShowTheFunctions proto :DWORD,:DWORD
AppendText proto :DWORD,:DWORD
SEH struct
PrevLink dd ? ; the address of the previous seh structure
CurrentHandler dd ? ; the address of the new exception handler
SafeOffset
dd ? ; The offset where it's safe to continue execution
PrevEsp dd ? ; the
old value in esp
PrevEbp dd ? ; The old value in ebp
SEH ends
.data
AppName db "PE tutorial no.6",0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
db "All Files",0,"*.*",0,0
FileOpenError db "Cannot
open the file for reading",0
FileOpenMappingError db "Cannot open
the file for memory mapping",0
FileMappingError db "Cannot map
the file into memory",0
NotValidPE db "This file is not a valid
PE",0
CRLF db 0Dh,0Ah,0
ImportDescriptor db 0Dh,0Ah,"================[
IMAGE_IMPORT_DESCRIPTOR ]=============",0
IDTemplate db "OriginalFirstThunk
= %lX",0Dh,0Ah
db "TimeDateStamp = %lX",0Dh,0Ah
db "ForwarderChain = %lX",0Dh,0Ah
db "Name = %s",0Dh,0Ah
db "FirstThunk = %lX",0
NameHeader db 0Dh,0Ah,"Hint Function",0Dh,0Ah
db "-----------------------------------------",0
NameTemplate db "%u %s",0
OrdinalTemplate db "%u (ord.)",0
.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?
.code
start:
invoke GetModuleHandle,NULL
invoke DialogBoxParam, eax, IDD_MAINDLG,NULL,addr DlgProc, 0
invoke
ExitProcess, 0
DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.if uMsg==WM_INITDIALOG
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETLIMITTEXT,0,0
.elseif uMsg==WM_CLOSE
invoke EndDialog,hDlg,0
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_OPEN
invoke ShowImportFunctions,hDlg
.else ; IDM_EXIT
invoke
SendMessage,hDlg,WM_CLOSE,0,0
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov
eax,TRUE
ret
DlgProc endp
SEHHandler proc uses edx pExcept:DWORD,
pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
mov edx,pFrame
assume edx:ptr SEH
mov eax,pContext
assume
eax:ptr CONTEXT
push [edx].SafeOffset
pop [eax].regEip
push [edx].PrevEsp
pop [eax].regEsp
push
[edx].PrevEbp
pop [eax].regEbp
mov ValidPE,
FALSE
mov eax,ExceptionContinueExecution
ret
SEHHandler endp
ShowImportFunctions proc uses edi hDlg:DWORD
LOCAL seh:SEH
mov ofn.lStructSize,SIZEOF
ofn mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET
buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST
or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL
.if eax!=INVALID_HANDLE_VALUE
mov hFile, eax
invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
.if eax!=NULL
mov hMapping, eax
invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
.if eax!=NULL
mov pMapping,eax
assume
fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandler,offset
SEHHandler
mov seh.SafeOffset,offset
FinalExit
lea eax,seh
mov fs:[0], eax
mov seh.PrevEsp,esp
mov seh.PrevEbp,ebp
mov edi, pMapping
assume edi:ptr IMAGE_DOS_HEADER
.if [edi].e_magic==IMAGE_DOS_SIGNATURE
add edi,
[edi].e_lfanew
assume edi:ptr IMAGE_NT_HEADERS
.if [edi].Signature==IMAGE_NT_SIGNATURE
mov
ValidPE, TRUE
.else
mov ValidPE, FALSE
.endif
.else
mov ValidPE,FALSE
.endif
FinalExit:
push seh.PrevLink
pop fs:[0]
.if ValidPE==TRUE
invoke ShowTheFunctions, hDlg, edi
.else
invoke MessageBox,0, addr NotValidPE, addr AppName, MB_OK+MB_ICONERROR
.endif
invoke UnmapViewOfFile,
pMapping
.else
invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
.endif
invoke
CloseHandle,hMapping
.else
invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
.endif
invoke CloseHandle, hFile
.else
invoke MessageBox, 0, addr FileOpenError,
addr AppName, MB_OK+MB_ICONERROR
.endif
.endif
ret
ShowImportFunctions endp
AppendText proc hDlg:DWORD,pText:DWORD
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,pText
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,addr
CRLF
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETSEL,-1,0
ret
AppendText endp
RVAToOffset PROC uses
edi esi edx ecx pFileMap:DWORD,RVA:DWORD
mov esi,pFileMap
assume esi:ptr IMAGE_DOS_HEADER
add esi,[esi].e_lfanew
assume esi:ptr IMAGE_NT_HEADERS
mov edi,RVA
; edi == RVA
mov edx,esi
add edx,sizeof IMAGE_NT_HEADERS
mov cx,[esi].FileHeader.NumberOfSections
movzx
ecx,cx
assume edx:ptr IMAGE_SECTION_HEADER
.while ecx>0 ; check all sections
.if edi>=[edx].VirtualAddress
mov eax,[edx].VirtualAddress
add eax,[edx].SizeOfRawData
.if edi<eax
; The address is in this section
mov
eax,[edx].VirtualAddress
sub edi,eax
mov eax,[edx].PointerToRawData
add eax,edi ; eax == file
offset
ret
.endif
.endif
add
edx,sizeof IMAGE_SECTION_HEADER
dec ecx
.endw
assume edx:nothing
assume esi:nothing
mov eax,edi
ret
RVAToOffset endp
ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
LOCAL temp[512]:BYTE
invoke SetDlgItemText,hDlg,IDC_EDIT,0
invoke AppendText,hDlg,addr buffer
mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory[sizeof
IMAGE_DATA_DIRECTORY].VirtualAddress
invoke RVAToOffset,pMapping,edi
mov edi,eax
add edi,pMapping
assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
.while !([edi].OriginalFirstThunk==0
&& [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 &&
[edi].Name1==0 && [edi].FirstThunk==0)
invoke
AppendText,hDlg,addr ImportDescriptor
invoke RVAToOffset,pMapping,
[edi].Name1
mov edx,eax
add edx,pMapping
invoke wsprintf, addr temp, addr
IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk
invoke AppendText,hDlg,addr temp
.if [edi].OriginalFirstThunk==0
mov esi,[edi].FirstThunk
.else
mov esi,[edi].OriginalFirstThunk
.endif
invoke RVAToOffset,pMapping,esi
add eax,pMapping
mov esi,eax
invoke
AppendText,hDlg,addr NameHeader
.while dword ptr
[esi]!=0
test dword ptr [esi],IMAGE_ORDINAL_FLAG32
jnz ImportByOrdinal
invoke RVAToOffset,pMapping,dword ptr [esi]
mov edx,eax
add edx,pMapping
assume edx:ptr IMAGE_IMPORT_BY_NAME
mov cx, [edx].Hint
movzx ecx,cx
invoke wsprintf,addr temp,addr NameTemplate,ecx,addr
[edx].Name1
jmp ShowTheText
ImportByOrdinal:
mov edx,dword ptr [esi]
and edx,0FFFFh
invoke wsprintf,addr
temp,addr OrdinalTemplate,edx
ShowTheText:
invoke AppendText,hDlg,addr temp
add
esi,4
.endw
add edi,sizeof IMAGE_IMPORT_DESCRIPTOR
.endw
ret
ShowTheFunctions endp
end start
分析:
本例中,使用者點選開啟選單顯示檔案開啟對話方塊,檢驗檔案的PE有效性後呼叫 ShowTheFunctions。
ShowTheFunctions
proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
LOCAL temp[512]:BYTE
保留512位元組堆疊空間用於字串操作。
invoke SetDlgItemText,hDlg,IDC_EDIT,0
清除編輯控制元件內容。
invoke AppendText,hDlg,addr buffer
將PE檔名插入編輯控制元件。 AppendText 透過傳遞一個 EM_REPLACESEL 訊息以通知向編輯控制元件新增文字。然後它又向編輯控制元件傳送一個設定了 wParam=-1和lParam=0的EM_SETSEL 訊息,使游標定位到文字末。
mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
獲取import symbols的RVA。edi起初指向 PE header,以此我們可以定位到資料目錄陣列的第二個陣列元素來得到虛擬地址值。
invoke RVAToOffset,pMapping,edi
mov edi,eax
add edi,pMapping
這兒對PE程式設計初學者來說可能有點困難。在PE檔案中大多數地址多是RVAs
而 RVAs只有當PE檔案被PE裝載器裝入記憶體後才有意義。
本例中,我們直接將檔案對映到記憶體而不是透過PE裝載器載入,因此我們不能直接使用那些RVAs。必須先將那些RVAs轉換成檔案偏移量,RVAToOffset函式就起到這個作用。
這裡不準備詳細分析。指出的是,它還將給定的RVA和PE檔案所有節的始末RVA作比較(檢驗RVA的有效性),然後透過IMAGE_SECTION_HEADER
結構中的PointerToRawData域(當然是所在節的那個PointerToRawData域啦)將RVA轉換成檔案偏移量。
函式使用需要傳遞兩個引數: 記憶體對映檔案指標和所要轉換的RVA。eax裡返回檔案偏移量。上面程式碼中,我們必須將檔案偏移量加上記憶體對映檔案指標以轉換成虛擬地址。是不是有點複雜?
:)
assume edi:ptr
IMAGE_IMPORT_DESCRIPTOR
.while !([edi].OriginalFirstThunk==0
&& [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 &&
[edi].Name1==0 && [edi].FirstThunk==0)
edi現在指向第一個 IMAGE_IMPORT_DESCRIPTOR 結構。接下來我們遍歷整個結構陣列直到遇上一個全0結構,這就是陣列末尾了。
invoke AppendText,hDlg,addr ImportDescriptor
invoke
RVAToOffset,pMapping, [edi].Name1
mov edx,eax
add edx,pMapping
我們要顯示當前 IMAGE_IMPORT_DESCRIPTOR 結構的值。Name1 不同於其他結構成員,它含有指向相關dll名的RVA。因此必須先將其轉換成虛擬地址。
invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk invoke AppendText,hDlg,addr temp
顯示當前 IMAGE_IMPORT_DESCRIPTOR 結構的值。
.if [edi].OriginalFirstThunk==0
mov esi,[edi].FirstThunk
.else
mov esi,[edi].OriginalFirstThunk
.endif
接下來準備遍歷 IMAGE_THUNK_DATA 陣列。通常我們會選擇OriginalFirstThunk指向的那個陣列,不過,如果某些聯結器錯誤地將OriginalFirstThunk 置0,這可以透過檢查OriginalFirstThunk值是否為0判斷。這樣的話,只要選擇FirstThunk指向的陣列了。
invoke RVAToOffset,pMapping,esi
add eax,pMapping
mov esi,eax
同樣的,OriginalFirstThunk/FirstThunk值是一個RVA。必須將其轉換為虛擬地址。
invoke AppendText,hDlg,addr NameHeader
.while dword
ptr [esi]!=0
現在我們準備遍歷 IMAGE_THUNK_DATAs 陣列以查詢該DLL引入的函式名,直到遇上全0項。
test dword ptr [esi],IMAGE_ORDINAL_FLAG32
jnz ImportByOrdinal
第一件事是校驗IMAGE_THUNK_DATA 是否含有IMAGE_ORDINAL_FLAG32標記。檢查IMAGE_THUNK_DATA 的MSB是否為1,如果是1,則函式是透過序數引出的,所以不需要更進一步處理了。直接從 IMAGE_THUNK_DATA 提取低位元組獲得序數,然後是下一個IMAGE_THUNK_DATA 雙字。
invoke RVAToOffset,pMapping,dword ptr [esi]
mov edx,eax
add edx,pMapping
assume edx:ptr IMAGE_IMPORT_BY_NAME
如果IMAGE_THUNK_DATA 的MSB是0,那麼它包含了IMAGE_IMPORT_BY_NAME 結構的RVA。需要先轉換為虛擬地址。
mov cx, [edx].Hint
movzx ecx,cx
invoke wsprintf,addr temp,addr NameTemplate,ecx,addr
[edx].Name1
jmp ShowTheText
Hint 是字型別,所以先轉換為雙字後再傳遞給wsprintf,然後我們將hint和函式名都顯示到編輯控制元件中。
ImportByOrdinal:
mov edx,dword ptr [esi]
and edx,0FFFFh
invoke wsprintf,addr
temp,addr OrdinalTemplate,edx
在僅用序數引出函式的情況中,先清空高字再顯示序數。
ShowTheText:
invoke AppendText,hDlg,addr temp
add esi,4
在編輯控制元件中插入相應的函式名/序數後,跳轉到下個 IMAGE_THUNK_DATA。
.endw
add edi,sizeof IMAGE_IMPORT_DESCRIPTOR
處理完當前IMAGE_THUNK_DATA 陣列裡的所有雙字,跳轉到下個IMAGE_IMPORT_DESCRIPTOR 開始處理其他DLLs的引入函式了。
附錄:
讓我們再來討論一下bound
import。當PE裝載器裝入PE檔案時,檢查引入表並將相關DLLs對映到程式地址空間。然後象我們這樣遍歷IMAGE_THUNK_DATA
陣列並用引入函式的真實地址替換IMAGE_THUNK_DATAs
值。這一步需要很多時間。如果程式設計師能事先正確預測函式地址,PE裝載器就不用每次裝入PE檔案時都去修正IMAGE_THUNK_DATAs
值了。Bound
import就是這種思想的產物。
為了方便實現,Microsoft出品的類似Visual
Studio的編譯器多提供了bind.exe這樣的工具,由它檢查PE檔案的引入表並用引入函式的真實地址替換IMAGE_THUNK_DATA
值。當檔案裝入時,PE裝載器必定檢查地址的有效性,如果DLL版本不同於PE檔案存放的相關資訊,或則DLLs需要重定位,那麼裝載器認為原先計算的地址是無效的,它必定遍歷OriginalFirstThunk指向的陣列以獲取引入函式新地址。
Bound import在本課中並非很重要,我們確省就是用到了OriginalFirstThunk。
翻譯:iamgufeng [Iczelion's Win32 Assembly Homepage][LuoYunBin's Win32 ASM Page]
相關文章
- PE教程5: Section Table(節表)2015-11-15
- PE教程7: Export Table(引出表)2015-11-15Export
- 少用@import引入CSS檔案2019-01-14ImportCSS
- import方法引入模組詳解2012-12-23Import
- 找TELock加殼的Import Table的方法 (6千字)2001-09-29Import
- PE檔案格式詳細解析(六)-- 基址重定位表(Base Relocation Table)2020-03-10
- link和@import引入css 區別,不建議使用@import2020-11-13ImportCSS
- Vue系列-import動態引入的坑2020-10-01VueImport
- 關於partition table import的問題2013-05-24Import
- PE教程2: 檢驗PE檔案的有效性2015-11-15
- ES6 import export2016-08-16ImportExport
- PE教程4: Optional Header2015-11-15Header
- 認識Import表2015-11-15Import
- Import表的重建2015-11-15Import
- MySQL Shell import_table資料匯入2021-08-05MySqlImport
- PE節表詳細分析2021-11-06
- gulp-html-import,在html中引入外部html檔案2018-12-11HTMLImport
- [6 kyu] Multiplication table2020-11-01
- oracle cache table(6)2009-02-19Oracle
- ES6 module模組 import export2020-12-11ImportExport
- <link>和@import url()引入外部css檔案的區別2017-03-23ImportCSS
- HTMLZip脫殼後的Import Table的修復 (750字)2001-02-10HTMLImport
- 重建EasyPDF v1.5.2 的import table (4千字)2001-06-09Import
- PE教程3: File Header (檔案頭)2015-11-15Header
- ES6 模組語法——import 命令2019-08-25Import
- ActiveReports 報表應用教程 (6)---分組報表2013-07-02
- 你真的理解@import和link引入樣式的區別嗎2018-03-22Import
- PE檔案結構(四) 輸出表2014-10-06
- 貼個初級問題,高手莫入:呼叫PE檔案中引入表中的函式的方法
(1千字)2015-11-15函式
- node識別es6的 import/export2018-08-30ImportExport
- diy pe的教學文章1 (6千字)2002-08-08
- 不再手寫import – VSCode自動引入Vue元件和Js模組2018-07-18ImportVSCodeVue元件JS
- oracle 外部表 external table2008-06-03Oracle
- Oracle外部表 External Table2011-09-09Oracle
- 分割槽表PARTITION table2007-12-25
- SQLAlchemy Table(表)類方式 – Table類和Column類2018-10-25SQL
- 胖爪裝機大師pe硬碟分割槽教程2022-04-15硬碟
- 詳解es6的export和import命令2021-10-12ExportImport