麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁(yè) > 學(xué)院 > 邏輯算法 > 正文

第一節(jié) PE文件格式

2019-09-10 09:02:21
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

 
第一節(jié) PE文件格式

教程1: PE文件格式一覽
考慮到早期寫的PE教程1是自己所有教程中最糟糕的一篇,此番決心徹底重寫一篇以饗讀者。
PE 的意思就是 Portable Executable(可移植的執(zhí)行體)。它是 Win32環(huán)境自身所帶的執(zhí)行體文件格式。它的一些特性繼承自 Unix的 Coff (common object file format)文件格式。"portable executable"(可移植的執(zhí)行體)意味著此文件格式是跨win32平臺(tái)的 : 即使Windows運(yùn)行在非Intel的CPU上,任何win32平臺(tái)的PE裝載器都能識(shí)別和使用該文件格式。當(dāng)然,移植到不同的CPU上PE執(zhí)行體必然得有一些改變。所有 win32執(zhí)行體 (除了VxD和16位的Dll)都使用PE文件格式,包括NT的內(nèi)核模式驅(qū)動(dòng)程序(kernel mode drivers)。因而研究PE文件格式給了我們洞悉Windows結(jié)構(gòu)的良機(jī)。

教程就讓我們?yōu)g覽一下 PE文件格式的概要。

DOS MZ header
DOS stub
PE header
Section table
Section 1
Section 2
Section ...
Section n

上圖是 PE文件結(jié)構(gòu)的總體層次分布。所有 PE文件(甚至32位的 DLLs) 必須以一個(gè)簡(jiǎn)單的 DOS MZ header 開始。我們通常對(duì)此結(jié)構(gòu)沒有太大興趣。有了它,一旦程序在DOS下執(zhí)行,DOS就能識(shí)別出這是有效的執(zhí)行體,然后運(yùn)行緊隨 MZ header 之后的 DOS stub。DOS stub實(shí)際上是個(gè)有效的 EXE,在不支持 PE文件格式的操作系統(tǒng)中,它將簡(jiǎn)單顯示一個(gè)錯(cuò)誤提示,類似于字符串 "This program requires Windows" 或者程序員可根據(jù)自己的意圖實(shí)現(xiàn)完整的 DOS代碼。通常我們也不對(duì) DOS stub 太感興趣: 因?yàn)榇蠖鄶?shù)情況下它是由匯編器/編譯器自動(dòng)生成。通常,它簡(jiǎn)單調(diào)用中斷21h服務(wù)9來(lái)顯示字符串"This program cannot run in DOS mode"。

緊接著 DOS stub 的是PE header。 PE header 是PE相關(guān)結(jié)構(gòu) IMAGE_NT_HEADERS 的簡(jiǎn)稱,其中包含了許多PE裝載器用到的重要域。當(dāng)我們更加深入研究PE文件格式后,將對(duì)這些重要域耳目能詳。執(zhí)行體在支持PE文件結(jié)構(gòu)的操作系統(tǒng)中執(zhí)行時(shí),PE裝載器將從 DOS MZ header 中找到 PE header 的起始偏移量。因而跳過(guò)了 DOS stub 直接定位到真正的文件頭 PE header。

PE文件的真正內(nèi)容劃分成塊,稱之為sections(節(jié))。每節(jié)是一塊擁有共同屬性的數(shù)據(jù),比如代碼/數(shù)據(jù)、讀/寫等。我們可以把PE文件想象成一邏輯磁盤,PE header 是磁盤的boot扇區(qū),而sections就是各種文件,每種文件自然就有不同屬性如只讀、系統(tǒng)、隱藏、文檔等等。 值得我們注意的是 ---- 節(jié)的劃分是基于各組數(shù)據(jù)的共同屬性: 而不是邏輯概念。重要的不是數(shù)據(jù)/代碼是如何使用的,如果PE文件中的數(shù)據(jù)/代碼擁有相同屬性,它們就能被歸入同一節(jié)中。不必關(guān)心節(jié)中類似于"data" "code"或其他的邏輯概念: 如果數(shù)據(jù)和代碼擁有相同屬性,它們就可以被歸入同一個(gè)節(jié)中。(譯者注:節(jié)名稱僅僅是個(gè)區(qū)別不同節(jié)的符號(hào)而已,類似"data" "code"的命名只為了便于識(shí)別,惟有節(jié)的屬性設(shè)置決定了節(jié)的特性和功能)如果某塊數(shù)據(jù)想付為只讀屬性,就可以將該塊數(shù)據(jù)放入置為只讀的節(jié)中,當(dāng)PE裝載器映射節(jié)內(nèi)容時(shí),它會(huì)檢查相關(guān)節(jié)屬性并置對(duì)應(yīng)內(nèi)存塊為指定屬性。

如果我們將PE文件格式視為一邏輯磁盤,PE header是boot扇區(qū)而sections是各種文件,但我們?nèi)匀狈ψ銐蛐畔?lái)定位磁盤上的不同文件,譬如,什么是PE文件格式中等價(jià)于目錄的東東?別急,那就是 PE header 接下來(lái)的數(shù)組結(jié)構(gòu)section table(節(jié)表)。 每個(gè)結(jié)構(gòu)包含對(duì)應(yīng)節(jié)的屬性、文件偏移量、虛擬偏移量等。如果PE文件里有5個(gè)節(jié),那么此結(jié)構(gòu)數(shù)組內(nèi)就有5個(gè)成員。因此,我們便可以把節(jié)表視為邏輯磁盤中的根目錄,每個(gè)數(shù)組成員等價(jià)于根目錄中目錄項(xiàng)。

以上就是PE文件格式的物理分布,下面將總結(jié)一下裝載一PE文件的主要步驟:

當(dāng)PE文件被執(zhí)行,PE裝載器檢查 DOS MZ header 里的 PE header 偏移量。如果找到,則跳轉(zhuǎn)到 PE header。
PE裝載器檢查 PE header 的有效性。如果有效,就跳轉(zhuǎn)到PE header的尾部。
緊跟 PE header 的是節(jié)表。PE裝載器讀取其中的節(jié)信息,并采用文件映射方法將這些節(jié)映射到內(nèi)存,同時(shí)付上節(jié)表里指定的節(jié)屬性。
PE文件映射入內(nèi)存后,PE裝載器將處理PE文件中類似 import table(引入表)邏輯部分。
上述步驟是基于本人觀察后的簡(jiǎn)述,顯然還有一些不夠精確的地方,但基本明晰了執(zhí)行體被處理的過(guò)程。

你應(yīng)該下載 LUEVELSMEYER的《PE文件格式》。 該文的描述相當(dāng)詳細(xì),可用作案頭的參考手冊(cè)。

PE教程2: 檢驗(yàn)PE文件的有效性
教程中我們將學(xué)習(xí)如何檢測(cè)給定文件是一有效PE文件。
下載 范例

理論:
如何才能校驗(yàn)指定文件是否為一有效PE文件呢? 這個(gè)問(wèn)題很難回答,完全取決于想要的精準(zhǔn)程度。您可以檢驗(yàn)PE文件格式里的各個(gè)數(shù)據(jù)結(jié)構(gòu),或者僅校驗(yàn)一些關(guān)鍵數(shù)據(jù)結(jié)構(gòu)。大多數(shù)情況下,沒有必要校驗(yàn)文件里的每一個(gè)數(shù)據(jù)結(jié)構(gòu),只要一些關(guān)鍵數(shù)據(jù)結(jié)構(gòu)有效,我們就認(rèn)為是有效的PE文件了。下面我們就來(lái)實(shí)現(xiàn)前面的假設(shè)。

我們要驗(yàn)證的重要數(shù)據(jù)結(jié)構(gòu)就是 PE header。從編程角度看,PE header 實(shí)際就是一個(gè) IMAGE_NT_HEADERS 結(jié)構(gòu)。定義如下:

IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS

Signature 一dword類型,值為50h 45h 00h 00h(PE/0/0)。 本域?yàn)镻E標(biāo)記,我們可以此識(shí)別給定文件是否為有效PE文件。
FileHeader 該結(jié)構(gòu)域包含了關(guān)于PE文件物理分布的信息, 比如節(jié)數(shù)目、文件執(zhí)行機(jī)器等。
OptionalHeader 該結(jié)構(gòu)域包含了關(guān)于PE文件邏輯分布的信息,雖然域名有"可選"字樣,但實(shí)際上本結(jié)構(gòu)總是存在的。

我們目的很明確。如果IMAGE_NT_HEADERS的signature域值等于"PE/0/0",那么就是有效的PE文件。實(shí)際上,為了比較方便,Microsoft已定義了常量IMAGE_NT_SIGNATURE供我們使用。 IMAGE_DOS_SIGNATURE equ 5A4Dh
IMAGE_OS2_SIGNATURE equ 454Eh
IMAGE_OS2_SIGNATURE_LE equ 454Ch
IMAGE_VXD_SIGNATURE equ 454Ch
IMAGE_NT_SIGNATURE equ 4550h

接下來(lái)的問(wèn)題是: 如何定位 PE header? 答案很簡(jiǎn)單: DOS MZ header 已經(jīng)包含了指向 PE header 的文件偏移量。DOS MZ header 又定義成結(jié)構(gòu)IMAGE_DOS_HEADER 。查詢windows.inc,我們知道 IMAGE_DOS_HEADER 結(jié)構(gòu)的e_lfanew成員就是指向 PE header 的文件偏移量。

現(xiàn)在將所有步驟總結(jié)如下:

首先檢驗(yàn)文件頭部第一個(gè)字的值是否等于 IMAGE_DOS_SIGNATURE,是則 DOS MZ header 有效。
一旦證明文件的 DOS header 有效后,就可用e_lfanew來(lái)定位 PE header 了。
比較 PE header 的第一個(gè)字的值是否等于IMAGE_NT_HEADER。如果前后兩個(gè)值都匹配,那我們就認(rèn)為該文件是一個(gè)有效的PE文件。
Example:
.386
.model flatstdcall
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

SEH struct
PrevLink dd ? ; the address of the previous seh structure
CurrentHandler dd ? ; the address of the 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.2"0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe *.dll)"0"*.exe;*.dll"0
db "All Files"0"*.*"00
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
FileValidPE db "This file is a valid PE"0
FileInValidPE db "This file is not a valid PE"0

.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?

.code
start proc
LOCAL seh:SEH
mov ofn.lStructSizeSIZEOF ofn
mov ofn.lpstrFilter OFFSET FilterString
mov ofn.lpstrFile OFFSET buffer
mov ofn.nMaxFile512
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_READONLY000
.if eax!=NULL
mov hMapping eax
invoke MapViewOfFilehMappingFILE_MAP_READ000
.if eax!=NULL
mov pMappingeax
assume fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandleroffset SEHHandler
mov seh.SafeOffsetoffset FinalExit
lea eaxseh
mov fs:[0] eax
mov seh.PrevEspesp
mov seh.PrevEbpebp
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 ValidPEFALSE
.endif
FinalExit:
.if ValidPE==TRUE
invoke MessageBox 0 addr FileValidPE addr AppName MB_OK+MB_ICONINFORMATION
.else
invoke MessageBox 0 addr FileInValidPE addr AppName MB_OK+MB_ICONINFORMATION
.endif
push seh.PrevLink
pop fs:[0]
invoke UnmapViewOfFile pMapping
.else
invoke MessageBox 0 addr FileMappingError addr AppName MB_OK+MB_ICONERROR
.endif
invoke CloseHandlehMapping
.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
invoke ExitProcess 0
start endp

SEHHandler proc uses edx pExcept:DWORD pFrame:DWORD pContext:DWORD pDispatch:DWORD
mov edxpFrame
assume edx:ptr SEH
mov eaxpContext
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 eaxExceptionContinueExecution
ret
SEHHandler endp
end start

分析:
本例程打開一文件,先檢驗(yàn)DOS header是否有效,有效就接著檢驗(yàn)PE header的有效性,ok就認(rèn)為是有效的PE文件了。這里,我們還運(yùn)用了結(jié)構(gòu)異常處理(SEH),這樣就不必檢查每個(gè)可能的錯(cuò)誤: 如果有錯(cuò)誤出現(xiàn),就認(rèn)為PE檢測(cè)失效所致,于是給出我們的報(bào)錯(cuò)信息。其實(shí)Windows內(nèi)部普遍使用SEH來(lái)檢驗(yàn)參數(shù)傳遞的有效性。若對(duì)SEH感興趣的話,可閱讀Jeremy Gordon的 文章。

程序調(diào)用打開文件通用對(duì)話框,用戶選定執(zhí)行文件后,程序便打開文件并映射到內(nèi)存。并在有效性檢驗(yàn)前建立一 SEH:

assume fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandleroffset SEHHandler
mov seh.SafeOffsetoffset FinalExit
lea eaxseh
mov fs:[0] eax
mov seh.PrevEspesp
mov seh.PrevEbpebp

一開始就假設(shè)寄存器 fs為空(assume fs:nothing)。 記住這一步不能省卻,因?yàn)镸ASM假設(shè)fs寄存器為ERROR。接下來(lái)保存 Windows使用的舊SEH處理函數(shù)地址到我們自己定義的結(jié)構(gòu)中,同時(shí)保存我們的SEH處理函數(shù)地址和異常處理時(shí)的執(zhí)行恢復(fù)地址,這樣一旦錯(cuò)誤發(fā)生就能由異常處理函數(shù)安全地恢復(fù)執(zhí)行了。同時(shí)還保存當(dāng)前esp及ebp的值,以便我們的SEH處理函數(shù)將堆棧恢復(fù)到正常狀態(tài)。

mov edi pMapping
assume edi:ptr IMAGE_DOS_HEADER
.if [edi].e_magic==IMAGE_DOS_SIGNATURE

成功建立SEH后繼續(xù)校驗(yàn)工作。置目標(biāo)文件的首字節(jié)地址給edi,使其指向DOS header的首字節(jié)。為便于比較,我們告訴編譯器可以假定edi正指向IMAGE_DOS_HEADER結(jié)構(gòu)(事實(shí)亦是如此)。然后比較DOS header的首字是否等于字符串"MZ",這里利用了windows.inc中定義的IMAGE_DOS_SIGNATURE常量。若比較成功,繼續(xù)轉(zhuǎn)到PE header,否則設(shè)ValidPE 值為FALSE,意味著文件不是有效PE文件。

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

要定位到PE header,需要讀取DOS header中的e_lfanew域值。該域含有PE header在文件中相對(duì)文件首部的偏移量。edi加上該值正好定位到PE header的首字節(jié)。這兒可能會(huì)出錯(cuò),如果文件不是PE文件,e_lfanew值就不正確,加上該值作為指針就可能導(dǎo)致異常。若不用SEH,我們必須校驗(yàn)e_lfanew值是否超出文件尺寸,這不是一個(gè)好辦法。如果一切OK,我們就比較PE header的首字是否是字符串"PE"。這里在此用到了常量IMAGE_NT_SIGNATURE,相等則認(rèn)為是有效的PE文件。
如果e_lfanew的值不正確導(dǎo)致異常,我們的SEH處理函數(shù)就得到執(zhí)行控制權(quán),簡(jiǎn)單恢復(fù)堆棧指針和基棧指針后,就根據(jù)safeoffset的值恢復(fù)執(zhí)行到FinalExit標(biāo)簽處。

FinalExit:
.if ValidPE==TRUE
invoke MessageBox 0 addr FileValidPE addr AppName MB_OK+MB_ICONINFORMATION
.else
invoke MessageBox 0 addr FileInValidPE addr AppName MB_OK+MB_ICONINFORMATION
.endif

上述代碼簡(jiǎn)單明確,根據(jù)ValidPE的值顯示相應(yīng)信息。

push seh.PrevLink
pop fs:[0]

一旦SEH不再使用,必須從SEH鏈上斷開。

翻譯:iamgufeng [Iczelion's Win32 Assembly Homepage][LuoYunBin's Win32 ASM Page]

PE教程3: File Header (文件頭)
本課我們將要研究 PE header 的 file header(文件頭)部分。

至此,我們已經(jīng)學(xué)到了哪些東東,先簡(jiǎn)要回顧一下:

DOS MZ header 又命名為IMAGE_DOS_HEADER.。其中只有兩個(gè)域比較重要: e_magic 包含字符串"MZ",e_lfanew 包含PE header在文件中的偏移量。
比較e_magic 是否為IMAGE_DOS_SIGNATURE以驗(yàn)證是否是有效的DOS header。比對(duì)符合則認(rèn)為文件擁有一個(gè)有效的DOS header。
為了定位PE header,移動(dòng)文件指針到e_lfanew所指向的偏移。
PE header的第一個(gè)雙字包含字符串"PE/0/0"。該雙字與IMAGE_NT_SIGNATURE比對(duì),符合則認(rèn)為PE header有效。
本課我們繼續(xù)討關(guān)于 PE header 的知識(shí)。 PE header 的正式命名是 IMAGE_NT_HEADERS。再來(lái)回憶一下這個(gè)結(jié)構(gòu)。

IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS

Signature PE標(biāo)記,值為50h 45h 00h 00h(PE/0/0)。
FileHeader 該結(jié)構(gòu)域包含了關(guān)于PE文件物理分布的一般信息。
OptionalHeader 該結(jié)構(gòu)域包含了關(guān)于PE文件邏輯分布的信息。

最有趣的東東在 OptionalHeader 里。不過(guò),F(xiàn)ileHeader 里的一些域也很重要。本課我們將學(xué)習(xí)FileHeader,下一課研究OptionalHeader。

IMAGE_FILE_HEADER STRUCT
Machine WORD ?
NumberOfSections WORD ?
TimeDateStamp dd ?
PointerToSymbolTable dd ?
NumberOfSymbols dd ?
SizeOfOptionalHeader WORD ?
Characteristics WORD ?
IMAGE_FILE_HEADER ENDS

Field name Meanings
Machine 該文件運(yùn)行所要求的CPU。對(duì)于Intel平臺(tái),該值是IMAGE_FILE_MACHINE_I386 (14Ch)。我們嘗試了LUEVELSMEYER的pe.txt聲明的14Dh和14Eh,但Windows不能正確執(zhí)行。看起來(lái),除了禁止程序執(zhí)行之外,本域?qū)ξ覀儊?lái)說(shuō)用處不大。
NumberOfSections 文件的節(jié)數(shù)目。如果我們要在文件中增加或刪除一個(gè)節(jié),就需要修改這個(gè)值。
TimeDateStamp 文件創(chuàng)建日期和時(shí)間。我們不感興趣。
PointerToSymbolTable 用于調(diào)試。
NumberOfSymbols 用于調(diào)試。
SizeOfOptionalHeader 指示緊隨本結(jié)構(gòu)之后的 OptionalHeader 結(jié)構(gòu)大小,必須為有效值。
Characteristics 關(guān)于文件信息的標(biāo)記,比如文件是exe還是dll。

簡(jiǎn)言之,只有三個(gè)域?qū)ξ覀冇幸恍┯? Machine NumberOfSections 和 Characteristics。通常不會(huì)改變 Machine 和Characteristics 的值,但如果要遍歷節(jié)表就得使用 NumberOfSections。
為了更好闡述 NumberOfSections 的用處,這里簡(jiǎn)要介紹一下節(jié)表。

節(jié)表是一個(gè)結(jié)構(gòu)數(shù)組,每個(gè)結(jié)構(gòu)包含一個(gè)節(jié)的信息。因此若有3個(gè)節(jié),數(shù)組就有3個(gè)成員。 我們需要NumberOfSections值來(lái)了解該數(shù)組中到底有幾個(gè)成員。 也許您會(huì)想檢測(cè)結(jié)構(gòu)中的全0成員起到同樣效果。Windows確實(shí)采用了這種方法。為了證明這一點(diǎn),可以增加NumberOfSections的值,Windows仍然可以正常執(zhí)行文件。據(jù)我們的觀察,Windows讀取NumberOfSections的值然后檢查節(jié)表里的每個(gè)結(jié)構(gòu),如果找到一個(gè)全0結(jié)構(gòu)就結(jié)束搜索,否則一直處理完NumberOfSections指定數(shù)目的結(jié)構(gòu)。 為什么我們不能忽略NumberOfSections的值? 有幾個(gè)原因。PE說(shuō)明中沒有指定節(jié)表必須以全0結(jié)構(gòu)結(jié)束。Thus there may be a situation where the last array member is contiguous to the first section without empty space at all. Another reason has to do with bound imports. The new-style binding puts the information immediately following the section table's last structure array member. 因此您仍然需要NumberOfSections。

翻譯:iamgufeng [Iczelion's Win32 Assembly Homepage][LuoYunBin's Win32 ASM Page]

PE教程4: Optional Header
我們已經(jīng)學(xué)習(xí)了關(guān)于 DOS header 和 PE header 中部分成員的知識(shí)。這里是 PE header 中最后、最大或許也是最重要的成員,optional header。

回顧一下,optional header 結(jié)構(gòu)是 IMAGE_NT_HEADERS 中的最后成員。包含了PE文件的邏輯分布信息。該結(jié)構(gòu)共有31個(gè)域,一些是很關(guān)鍵,另一些不太常用。這里只介紹那些真正有用的域。

這兒有個(gè)關(guān)于PE文件格式的常用術(shù)語(yǔ): RVA
RVA 代表相對(duì)虛擬地址。 知道什么是虛擬地址嗎?相對(duì)那些簡(jiǎn)單的概念而言,RVA有些晦澀。簡(jiǎn)言之,RVA是虛擬空間中到參考點(diǎn)的一段距離。我打賭您肯定熟悉文件偏移量: RVA就是類似文件偏移量的東西。當(dāng)然它是相對(duì)虛擬空間里的一個(gè)地址,而不是文件頭部。舉例說(shuō)明,如果PE文件裝入虛擬地址(VA)空間的400000h處,且進(jìn)程從虛址401000h開始執(zhí)行,我們可以說(shuō)進(jìn)程執(zhí)行起始地址在RVA 1000h。每個(gè)RVA都是相對(duì)于模塊的起始VA的。
為什么PE文件格式要用到RVA呢? 這是為了減少PE裝載器的負(fù)擔(dān)。因?yàn)槊總€(gè)模塊多有可能被重載到任何虛擬地址空間,如果讓PE裝載器修正每個(gè)重定位項(xiàng),這肯定是個(gè)夢(mèng)魘。相反,如果所有重定位項(xiàng)都使用RVA,那么PE裝載器就不必操心那些東西了: 它只要將整個(gè)模塊重定位到新的起始VA。這就象相對(duì)路徑和絕對(duì)路徑的概念: RVA類似相對(duì)路徑,VA就象絕對(duì)路徑。

Field Meanings
AddressOfEntryPoint PE裝載器準(zhǔn)備運(yùn)行的PE文件的第一個(gè)指令的RVA。若您要改變整個(gè)執(zhí)行的流程,可以將該值指定到新的RVA,這樣新RVA處的指令首先被執(zhí)行。
ImageBase PE文件的優(yōu)先裝載地址。比如,如果該值是400000h,PE裝載器將嘗試把文件裝到虛擬地址空間的400000h處。字眼"優(yōu)先"表示若該地址區(qū)域已被其他模塊占用,那PE裝載器會(huì)選用其他空閑地址。
SectionAlignment 內(nèi)存中節(jié)對(duì)齊的粒度。例如,如果該值是4096 (1000h),那么每節(jié)的起始地址必須是4096的倍數(shù)。若第一節(jié)從401000h開始且大小是10個(gè)字節(jié),則下一節(jié)必定從402000h開始,即使401000h和402000h之間還有很多空間沒被使用。
FileAlignment 文件中節(jié)對(duì)齊的粒度。例如,如果該值是(200h),那么每節(jié)的起始地址必須是512的倍數(shù)。若第一節(jié)從文件偏移量200h開始且大小是10個(gè)字節(jié),則下一節(jié)必定位于偏移量400h: 即使偏移量512和1024之間還有很多空間沒被使用/定義。

MajorSubsystemVersion
MinorSubsystemVersion win32子系統(tǒng)版本。若PE文件是專門為Win32設(shè)計(jì)的,該子系統(tǒng)版本必定是4.0否則對(duì)話框不會(huì)有3維立體感。
SizeOfImage 內(nèi)存中整個(gè)PE映像體的尺寸。它是所有頭和節(jié)經(jīng)過(guò)節(jié)對(duì)齊處理后的大小。
SizeOfHeaders 所有頭+節(jié)表的大小,也就等于文件尺寸減去文件中所有節(jié)的尺寸。可以以此值作為PE文件第一節(jié)的文件偏移量。
Subsystem NT用來(lái)識(shí)別PE文件屬于哪個(gè)子系統(tǒng)。 對(duì)于大多數(shù)Win32程序,只有兩類值: Windows GUI 和 Windows CUI (控制臺(tái))。
DataDirectory 一IMAGE_DATA_DIRECTORY 結(jié)構(gòu)數(shù)組。每個(gè)結(jié)構(gòu)給出一個(gè)重要數(shù)據(jù)結(jié)構(gòu)的RVA,比如引入地址表等。

翻譯:iamgufeng [Iczelion's Win32 Assembly Homepage][LuoYunBin's Win32 ASM Page]

PE教程5: Section Table(節(jié)表)
請(qǐng)下載 范例。

理論:
到本課為止,我們已經(jīng)學(xué)了許多關(guān)于 DOS header 和 PE header 的知識(shí)。接下來(lái)就該輪到 section table(節(jié)表)了。節(jié)表其實(shí)就是緊挨著 PE header 的一結(jié)構(gòu)數(shù)組。該數(shù)組成員的數(shù)目由 file header (IMAGE_FILE_HEADER) 結(jié)構(gòu)中 NumberOfSections 域的域值來(lái)決定。節(jié)表結(jié)構(gòu)又命名為 IMAGE_SECTION_HEADER。

IMAGE_SIZEOF_SHORT_NAME equ 8

IMAGE_SECTION_HEADER STRUCT
Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)
union Misc
PhysicalAddress dd ?
VirtualSize dd ?
ends
VirtualAddress dd ?
SizeOfRawData dd ?
PointerToRawData dd ?
PointerToRelocations dd ?
PointerToLinenumbers dd ? 哦
NumberOfRelocations dw ?
NumberOfLinenumbers dw ?
Characteristics dd ?
IMAGE_SECTION_HEADER ENDS

同樣,不是所有成員都是很有用的,我們只關(guān)心那些真正重要的。

Field Meanings
Name1 事實(shí)上本域的名稱是"name",只是"name"已被MASM用作關(guān)鍵字,所以我們只能用"Name1"代替。這兒的節(jié)名長(zhǎng)不超過(guò)8字節(jié)。記住節(jié)名僅僅是個(gè)標(biāo)記而已,我們選擇任何名字甚至空著也行,注意這里不用null結(jié)束。命名不是一個(gè)ASCIIZ字符串,所以不用null結(jié)尾。
VirtualAddress 本節(jié)的RVA(相對(duì)虛擬地址)。PE裝載器將節(jié)映射至內(nèi)存時(shí)會(huì)讀取本值,因此如果域值是1000h,而PE文件裝在地址400000h處,那么本節(jié)就被載到401000h。
SizeOfRawData 經(jīng)過(guò)文件對(duì)齊處理后節(jié)尺寸,PE裝載器提取本域值了解需映射入內(nèi)存的節(jié)字節(jié)數(shù)。(譯者注: 假設(shè)一個(gè)文件的文件對(duì)齊尺寸是0x200,如果前面的 VirtualSize域指示本節(jié)長(zhǎng)度是0x388字節(jié),則本域值為0x400,表示本節(jié)是0x400字節(jié)長(zhǎng))。
PointerToRawData 這是節(jié)基于文件的偏移量,PE裝載器通過(guò)本域值找到節(jié)數(shù)據(jù)在文件中的位置。
Characteristics 包含標(biāo)記以指示節(jié)屬性,比如節(jié)是否含有可執(zhí)行代碼、初始化數(shù)據(jù)、未初始數(shù)據(jù),是否可寫、可讀等。

現(xiàn)在我們已知曉 IMAGE_SECTION_HEADER 結(jié)構(gòu),再來(lái)模擬一下 PE裝載器的工作吧:

讀取 IMAGE_FILE_HEADER 的 NumberOfSections域,知道文件的節(jié)數(shù)目。
SizeOfHeaders 域值作為節(jié)表的文件偏移量,并以此定位節(jié)表。
遍歷整個(gè)結(jié)構(gòu)數(shù)組檢查各成員值。
對(duì)于每個(gè)結(jié)構(gòu),我們讀取PointerToRawData域值并定位到該文件偏移量。然后再讀取SizeOfRawData域值來(lái)決定映射內(nèi)存的字節(jié)數(shù)。將VirtualAddress域值加上ImageBase域值等于節(jié)起始的虛擬地址。然后就準(zhǔn)備把節(jié)映射進(jìn)內(nèi)存,并根據(jù)Characteristics域值設(shè)置屬性。
遍歷整個(gè)數(shù)組,直至所有節(jié)都已處理完畢。
注意我們并沒有使用節(jié)名: 這其實(shí)并不重要。

示例:
本例程打開一PE文件遍歷其節(jié)表,并在列表框控件顯示各節(jié)的信息。

.386
.model flatstdcall
option casemap:none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/comdlg32.inc
include /masm32/include/user32.inc
include /masm32/include/comctl32.inc
includelib /masm32/lib/comctl32.lib
includelib /masm32/lib/user32.lib
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/comdlg32.lib

IDD_SECTIONTABLE equ 104
IDC_SECTIONLIST equ 1001

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.5"0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe *.dll)"0"*.exe;*.dll"0
db "All Files"0"*.*"00
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
FileInValidPE db "This file is not a valid PE"0
template db "%08lx"0
SectionName db "Section"0
VirtualSize db "V.Size"0
VirtualAddress db "V.Address"0
SizeOfRawData db "Raw Size"0
RawOffset db "Raw Offset"0
Characteristics db "Characteristics"0

.data?
hInstance dd ?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?
NumberOfSections dd ?

.code
start proc
LOCAL seh:SEH
invoke GetModuleHandleNULL
mov hInstanceeax
mov ofn.lStructSizeSIZEOF ofn
mov ofn.lpstrFilter OFFSET FilterString
mov ofn.lpstrFile OFFSET buffer
mov ofn.nMaxFile512
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_READONLY000
.if eax!=NULL
mov hMapping eax
invoke MapViewOfFilehMappingFILE_MAP_READ000
.if eax!=NULL
mov pMappingeax
assume fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandleroffset SEHHandler
mov seh.SafeOffsetoffset FinalExit
lea eaxseh
mov fs:[0] eax
mov seh.PrevEspesp
mov seh.PrevEbpebp
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 ValidPEFALSE
.endif
FinalExit:
push seh.PrevLink
pop fs:[0]
.if ValidPE==TRUE
call ShowSectionInfo
.else
invoke MessageBox 0 addr FileInValidPE addr AppName MB_OK+MB_ICONINFORMATION
.endif
invoke UnmapViewOfFile pMapping
.else
invoke MessageBox 0 addr FileMappingError addr AppName MB_OK+MB_ICONERROR
.endif
invoke CloseHandlehMapping
.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
invoke ExitProcess 0
invoke InitCommonControls
start endp

SEHHandler proc uses edx pExcept:DWORDpFrame:DWORDpContext:DWORDpDispatch:DWORD
mov edxpFrame
assume edx:ptr SEH
mov eaxpContext
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 eaxExceptionContinueExecution
ret
SEHHandler endp

DlgProc proc uses edi esi hDlg:DWORD uMsg:DWORD wParam:DWORD lParam:DWORD
LOCAL lvc:LV_COLUMN
LOCAL lvi:LV_ITEM
.if uMsg==WM_INITDIALOG
mov esi lParam
mov lvc.imaskLVCF_FMT or LVCF_TEXT or LVCF_WIDTH or LVCF_SUBITEM
mov lvc.fmtLVCFMT_LEFT
mov lvc.lx80
mov lvc.iSubItem0
mov lvc.pszTextoffset SectionName
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_INSERTCOLUMN0addr lvc inc lvc.iSubItem
mov lvc.fmtLVCFMT_RIGHT
mov lvc.pszTextoffset VirtualSize
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_INSERTCOLUMN1addr lvc
inc lvc.iSubItem
mov lvc.pszTextoffset VirtualAddress
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_INSERTCOLUMN2addr lvc
inc lvc.iSubItem
mov lvc.pszTextoffset SizeOfRawData
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_INSERTCOLUMN3addr lvc
inc lvc.iSubItem
mov lvc.pszTextoffset RawOffset
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_INSERTCOLUMN4addr lvc
inc lvc.iSubItem
mov lvc.pszTextoffset Characteristics
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_INSERTCOLUMN5addr lvc
mov ax NumberOfSections
movzx eaxax
mov edieax
mov lvi.imaskLVIF_TEXT
mov lvi.iItem0
assume esi:ptr IMAGE_SECTION_HEADER
.while edi>0
mov lvi.iSubItem0
invoke RtlZeroMemoryaddr buffer9
invoke lstrcpynaddr bufferaddr [esi].Name18
lea eaxbuffer
mov lvi.pszTexteax
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_INSERTITEM0addr lvi
invoke wsprintfaddr bufferaddr template[esi].Misc.VirtualSize
lea eaxbuffer
mov lvi.pszTexteax
inc lvi.iSubItem
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_SETITEM0addr lvi
invoke wsprintfaddr bufferaddr template[esi].VirtualAddress
lea eaxbuffer
mov lvi.pszTexteax
inc lvi.iSubItem
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_SETITEM0addr lvi
invoke wsprintfaddr bufferaddr template[esi].SizeOfRawData
lea eaxbuffer
mov lvi.pszTexteax
inc lvi.iSubItem
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_SETITEM0addr lvi
invoke wsprintfaddr bufferaddr template[esi].PointerToRawData
lea eaxbuffer
mov lvi.pszTexteax
inc lvi.iSubItem
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_SETITEM0addr lvi
invoke wsprintfaddr bufferaddr template[esi].Characteristics
lea eaxbuffer
mov lvi.pszTexteax
inc lvi.iSubItem
invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_SETITEM0addr lvi
inc lvi.iItem
dec edi
add esi sizeof IMAGE_SECTION_HEADER
.endw
.elseif
uMsg==WM_CLOSE
invoke EndDialoghDlgNULL
.else
mov eaxFALSE
ret
.endif
mov eaxTRUE
ret
DlgProc endp

ShowSectionInfo proc uses edi
mov edi pMapping
assume edi:ptr IMAGE_DOS_HEADER
add edi [edi].e_lfanew
assume edi:ptr IMAGE_NT_HEADERS
mov ax[edi].FileHeader.NumberOfSections
movzx eaxax
mov NumberOfSectionseax
add edisizeof IMAGE_NT_HEADERS
invoke DialogBoxParam hInstance IDD_SECTIONTABLENULL addr DlgProc edi
ret
ShowSectionInfo endp
end start

分析:
本例重用了PE教程2的代碼,校驗(yàn)PE文件的有效性后,繼續(xù)調(diào)用函數(shù)ShowSectionInfo顯示各節(jié)信息。

ShowSectionInfo proc uses edi
mov edi pMapping
assume edi:ptr IMAGE_DOS_HEADER
add edi [edi].e_lfanew
assume edi:ptr IMAGE_NT_HEADERS

我們將edi用作指向PE文件數(shù)據(jù)的指針。首先,將指向DOS header地址的pMapping賦給edi,再加上e_lfanew域值等于PE header的地址。

mov ax[edi].FileHeader.NumberOfSections
mov NumberOfSectionsax

因?yàn)槲覀円闅v節(jié)表,所以必須先獲取文件的節(jié)數(shù)目。這就得靠file header里的NumberOfSections域了,切記這是個(gè)word域。

add edisizeof IMAGE_NT_HEADERS

現(xiàn)在edi正指向PE header的起始地址,加上PE header結(jié)構(gòu)大小后恰好指向節(jié)表了。

invoke DialogBoxParam hInstance IDD_SECTIONTABLENULL addr DlgProc edi

調(diào)用 DialogBoxParam 顯示列表對(duì)話框,注意我們已將節(jié)表地址作為最后一個(gè)參數(shù)傳遞過(guò)去了,該值可從WM_INITDIALOG 消息的lParam參數(shù)中提取。

在對(duì)話框過(guò)程里我們響應(yīng)WM_INITDIALOG消息,將lParam值 (節(jié)表地址)存入esi,節(jié)數(shù)目賦給edi并設(shè)置列表控件。萬(wàn)事俱備后,進(jìn)入循環(huán)將各節(jié)信息插入到列表控件中,這部分相當(dāng)簡(jiǎn)單。 .while edi>0
mov lvi.iSubItem0

字符串置入第一列。

invoke RtlZeroMemoryaddr buffer9
invoke lstrcpynaddr bufferaddr [esi].Name18
lea eaxbuffer
mov lvi.pszTexteax

要顯示節(jié)名,當(dāng)然要將其轉(zhuǎn)換為ASCIIZ字符串先。

invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_INSERTITEM0addr lvi

然后顯示第一列。
繼續(xù)我們偉大的工程,顯示完本節(jié)中最后一個(gè)欲呈現(xiàn)的值后,立馬下一個(gè)結(jié)構(gòu)。 dec edi
add esi sizeof IMAGE_SECTION_HEADER
.endw

每處理完一節(jié)就遞減edi,然后將esi加上IMAGE_SECTION_HEADER 結(jié)構(gòu)大小,使其指向下一個(gè)IMAGE_SECTION_HEADER 結(jié)構(gòu)。

遍歷節(jié)表的步驟:

PE文件有效性校驗(yàn)。
定位到 PE header 的起始地址。
從 file header 的NumberOfSections域獲取節(jié)數(shù)。
通過(guò)兩種方法定位節(jié)表: ImageBase+SizeOfHeaders 或者 PE header的起始地址+ PE header結(jié)構(gòu)大小。 (節(jié)表緊隨 PE header)。如果不是使用文件映射的方法,可以用SetFilePointer 直接將文件指針定位到節(jié)表。節(jié)表的文件偏移量存放在 SizeOfHeaders域里。(SizeOfHeaders 是 IMAGE_OPTIONAL_HEADER 的結(jié)構(gòu)成員)
處理每個(gè) IMAGE_SECTION_HEADER 結(jié)構(gòu)。
翻譯:iamgufeng [Iczelion's Win32 Assembly Homepage][LuoYunBin's Win32 ASM Page]

PE教程6: Import Table(引入表)
本課我們將學(xué)習(xí)引入表。先警告一下,對(duì)于不熟悉引入表的讀者來(lái)說(shuō),這是一堂又長(zhǎng)又難的課,所以需要多讀幾遍,最好再打開調(diào)試器來(lái)好好分析相關(guān)結(jié)構(gòu)。各位,努力啊!

下載范例。

理論:
首先,您得了解什么是引入函數(shù)。一個(gè)引入函數(shù)是被某模塊調(diào)用的但又不在調(diào)用者模塊中的函數(shù),因而命名為"import(引入)"。引入函數(shù)實(shí)際位于一個(gè)或者更多的DLL里。調(diào)用者模塊里只保留一些函數(shù)信息,包括函數(shù)名及其駐留的DLL名。現(xiàn)在,我們?cè)鯓硬拍苷业絇E文件中保存的信息呢? 轉(zhuǎn)到data directory 尋求答案吧。再回顧一把,下面就是 PE header:

IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER <>
IMAGE_NT_HEADERS ENDS

optional header 最后一個(gè)成員就是 data directory(數(shù)據(jù)目錄):

IMAGE_OPTIONAL_HEADER32 STRUCT
....
LoaderFlags dd ?
NumberOfRvaAndSizes dd ?
DataDirectory IMAGE_DATA_DIRECTORY 16 dup(<>)
IMAGE_OPTIONAL_HEADER32 ENDS

data directory 是一個(gè) IMAGE_DATA_DIRECTORY 結(jié)構(gòu)數(shù)組,共有16個(gè)成員。如果您還記得節(jié)表可以看作是PE文件各節(jié)的根目錄的話,也可以認(rèn)為 data directory 是存儲(chǔ)在這些節(jié)里的邏輯元素的根目錄。明確點(diǎn),data directory 包含了PE文件中各重要數(shù)據(jù)結(jié)構(gòu)的位置和尺寸信息。 每個(gè)成員包含了一個(gè)重要數(shù)據(jù)結(jié)構(gòu)的信息。

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 包含域后,我們可以仔細(xì)研究它們了。data directory 的每個(gè)成員都是 IMAGE_DATA_DIRECTORY 結(jié)構(gòu)類型的,其定義如下所示:

IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress dd ?
isize dd ?
IMAGE_DATA_DIRECTORY ENDS

VirtualAddress 實(shí)際上是數(shù)據(jù)結(jié)構(gòu)的相對(duì)虛擬地址(RVA)。比如,如果該結(jié)構(gòu)是關(guān)于import symbols的,該域就包含指向IMAGE_IMPORT_DESCRIPTOR 數(shù)組的RVA。
isize 含有VirtualAddress所指向數(shù)據(jù)結(jié)構(gòu)的字節(jié)數(shù)。

下面就是如何找尋PE文件中重要數(shù)據(jù)結(jié)構(gòu)的一般方法:

從 DOS header 定位到 PE header
從 optional header 讀取 data directory 的地址。
IMAGE_DATA_DIRECTORY 結(jié)構(gòu)尺寸乘上找尋結(jié)構(gòu)的索引號(hào): 比如您要找尋import symbols的位置信息,必須用IMAGE_DATA_DIRECTORY 結(jié)構(gòu)尺寸(8 bytes)乘上1(import symbols在data directory中的索引號(hào))。
將上面的結(jié)果加上data directory地址,我們就得到包含所查詢數(shù)據(jù)結(jié)構(gòu)信息的 IMAGE_DATA_DIRECTORY 結(jié)構(gòu)項(xiàng)。
現(xiàn)在我們開始真正討論引入表了。data directory數(shù)組第二項(xiàng)的VirtualAddress包含引入表地址。引入表實(shí)際上是一個(gè) IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)數(shù)組。每個(gè)結(jié)構(gòu)包含PE文件引入函數(shù)的一個(gè)相關(guān)DLL的信息。比如,如果該P(yáng)E文件從10個(gè)不同的DLL中引入函數(shù),那么這個(gè)數(shù)組就有10個(gè)成員。該數(shù)組以一個(gè)全0的成員結(jié)尾。下面詳細(xì)研究結(jié)構(gòu)組成:

IMAGE_IMPORT_DESCRIPTOR STRUCT
union
Characteristics dd ?
OriginalFirstThunk dd ?
ends
TimeDateStamp dd ?
ForwarderChain dd ?
Name1 dd ?
FirstThunk dd ?
IMAGE_IMPORT_DESCRIPTOR ENDS

結(jié)構(gòu)第一項(xiàng)是一個(gè)union子結(jié)構(gòu)。 事實(shí)上,這個(gè)union子結(jié)構(gòu)只是給 OriginalFirstThunk 增添了個(gè)別名,您也可以稱其為"Characteristics"。 該成員項(xiàng)含有指向一個(gè) IMAGE_THUNK_DATA 結(jié)構(gòu)數(shù)組的RVA。
什么是 IMAGE_THUNK_DATA? 這是一個(gè)dword類型的集合。通常我們將其解釋為指向一個(gè) IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)的指針。注意 IMAGE_THUNK_DATA 包含了指向一個(gè) IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)的指針: 而不是結(jié)構(gòu)本身。
請(qǐng)看這里: 現(xiàn)有幾個(gè) IMAGE_IMPORT_BY_NAME 結(jié)構(gòu),我們收集起這些結(jié)構(gòu)的RVA (IMAGE_THUNK_DATAs)組成一個(gè)數(shù)組,并以0結(jié)尾,然后再將數(shù)組的RVA放入 OriginalFirstThunk。
此 IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)存有一個(gè)引入函數(shù)的相關(guān)信息。再來(lái)研究 IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)到底是什么樣子的呢:

IMAGE_IMPORT_BY_NAME STRUCT
Hint dw ?
Name1 db ?
IMAGE_IMPORT_BY_NAME ENDS

Hint 指示本函數(shù)在其所駐留DLL的引出表中的索引號(hào)。該域被PE裝載器用來(lái)在DLL的引出表里快速查詢函數(shù)。該值不是必須的,一些連接器將此值設(shè)為0。
Name1 含有引入函數(shù)的函數(shù)名。函數(shù)名是一個(gè)ASCIIZ字符串。注意這里雖然將Name1的大小定義成字節(jié),其實(shí)它是可變尺寸域,只不過(guò)我們沒有更好方法來(lái)表示結(jié)構(gòu)中的可變尺寸域。The structure is provided so that you can refer to the data structure with descriptive names.


TimeDateStamp 和 ForwarderChain 可是高級(jí)東東: 讓我們精通其他成員后再來(lái)討論它們吧。

Name1 含有指向DLL名字的RVA,即指向DLL名字的指針,也是一個(gè)ASCIIZ字符串。

FirstThunk 與 OriginalFirstThunk 非常相似,它也包含指向一個(gè) IMAGE_THUNK_DATA 結(jié)構(gòu)數(shù)組的RVA(當(dāng)然這是另外一個(gè)IMAGE_THUNK_DATA 結(jié)構(gòu)數(shù)組)。
好了,如果您還在犯糊涂,就朝這邊看過(guò)來(lái): 現(xiàn)在有幾個(gè) IMAGE_IMPORT_BY_NAME 結(jié)構(gòu),同時(shí)您又創(chuàng)建了兩個(gè)結(jié)構(gòu)數(shù)組,并同樣寸入指向那些 IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)的RVAs,這樣兩個(gè)數(shù)組就包含相同數(shù)值了(可謂相當(dāng)精確的復(fù)制啊)。 最后您決定將第一個(gè)數(shù)組的RVA賦給 OriginalFirstThunk,第二個(gè)數(shù)組的RVA賦給 FirstThunk,這樣一切都很清楚了。

OriginalFirstThunk   IMAGE_IMPORT_BY_NAME   FirstThunk
|
      |
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA
--->
--->
--->
--->
--->
--->
Function 1
Function 2
Function 3
Function 4
...
Function n
<---
<---
<---
<---
<---
<---
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA


現(xiàn)在您應(yīng)該明白我的意思。不要被IMAGE_THUNK_DATA這個(gè)名字弄糊涂: 它僅是指向 IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)的RVA。 如果將 IMAGE_THUNK_DATA 字眼想象成RVA,就更容易明白了。OriginalFirstThunk 和 FirstThunk 所指向的這兩個(gè)數(shù)組大小取決于PE文件從DLL中引入函數(shù)的數(shù)目。比如,如果PE文件從kernel32.dll中引入10個(gè)函數(shù),那么IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)的 Name1域包含指向字符串"kernel32.dll"的RVA,同時(shí)每個(gè)IMAGE_THUNK_DATA 數(shù)組有10個(gè)元素。

下一個(gè)問(wèn)題是: 為什么我們需要兩個(gè)完全相同的數(shù)組? 為了回答該問(wèn)題,我們需要了解當(dāng)PE文件被裝載到內(nèi)存時(shí),PE裝載器將查找IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 這些結(jié)構(gòu)數(shù)組,以此決定引入函數(shù)的地址。然后用引入函數(shù)真實(shí)地址來(lái)替代由FirstThunk指向的 IMAGE_THUNK_DATA 數(shù)組里的元素值。因此當(dāng)PE文件準(zhǔn)備執(zhí)行時(shí),上圖已轉(zhuǎn)換成:

OriginalFirstThunk   IMAGE_IMPORT_BY_NAME   FirstThunk
|
      |
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA
--->
--->
--->
--->
--->
--->
Function 1
Function 2
Function 3
Function 4
...
Function n

 
 
 
 
 
Address of Function 1
Address of Function 2
Address of Function 3
Address of Function 4
...
Address of Function n


由OriginalFirstThunk 指向的RVA數(shù)組始終不會(huì)改變,所以若還反過(guò)頭來(lái)查找引入函數(shù)名,PE裝載器還能找尋到。
當(dāng)然再簡(jiǎn)單的事物都有其復(fù)雜的一面。有些情況下一些函數(shù)僅由序數(shù)引出,也就是說(shuō)您不能用函數(shù)名來(lái)調(diào)用它們: 您只能用它們的位置來(lái)調(diào)用。此時(shí),調(diào)用者模塊中就不存在該函數(shù)的IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)。不同的,對(duì)應(yīng)該函數(shù)的 IMAGE_THUNK_DATA 值的低位字指示函數(shù)序數(shù),而最高二進(jìn)位 (MSB)設(shè)為1。例如,如果一個(gè)函數(shù)只由序數(shù)引出且其序數(shù)是1234h,那么對(duì)應(yīng)該函數(shù)的 IMAGE_THUNK_DATA 值是80001234h。Microsoft提供了一個(gè)方便的常量來(lái)測(cè)試dword值的MSB位,就是 IMAGE_ORDINAL_FLAG32,其值為80000000h。
假設(shè)我們要列出某個(gè)PE文件的所有引入函數(shù),可以照著下面步驟走:

校驗(yàn)文件是否是有效的PE。
從 DOS header 定位到 PE header。
獲取位于 OptionalHeader 數(shù)據(jù)目錄地址。
轉(zhuǎn)至數(shù)據(jù)目錄的第二個(gè)成員提取其VirtualAddress值。
利用上值定位第一個(gè) IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)。
檢查 OriginalFirstThunk值。若不為0,順著 OriginalFirstThunk 里的RVA值轉(zhuǎn)入那個(gè)RVA數(shù)組。若 OriginalFirstThunk 為0,就改用FirstThunk值。有些連接器生成PE文件時(shí)會(huì)置OriginalFirstThunk值為0,這應(yīng)該算是個(gè)bug。不過(guò)為了安全起見,我們還是檢查 OriginalFirstThunk值先。
對(duì)于每個(gè)數(shù)組元素,我們比對(duì)元素值是否等于IMAGE_ORDINAL_FLAG32。如果該元素值的最高二進(jìn)位為1, 那么函數(shù)是由序數(shù)引入的,可以從該值的低字節(jié)提取序數(shù)。
如果元素值的最高二進(jìn)位為0,就可將該值作為RVA轉(zhuǎn)入 IMAGE_IMPORT_BY_NAME 數(shù)組,跳過(guò) Hint 就是函數(shù)名字了。
再跳至下一個(gè)數(shù)組元素提取函數(shù)名一直到數(shù)組底部(它以null結(jié)尾)。現(xiàn)在我們已遍歷完一個(gè)DLL的引入函數(shù),接下去處理下一個(gè)DLL。
即跳轉(zhuǎn)到下一個(gè) IMAGE_IMPORT_DESCRIPTOR 并處理之,如此這般循環(huán)直到數(shù)組見底。(IMAGE_IMPORT_DESCRIPTOR 數(shù)組以一個(gè)全0域元素結(jié)尾)。
示例:
本例程打開一PE文件,將所有引入函數(shù)名讀入一編輯控件,同時(shí)顯示 IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)各域值。

.386
.model flatstdcall
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"*.*"00
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 0Dh0Ah0
ImportDescriptor db 0Dh0Ah"================[ IMAGE_IMPORT_DESCRIPTOR ]============="0
IDTemplate db "OriginalFirstThunk = %lX"0Dh0Ah
db "TimeDateStamp = %lX"0Dh0Ah
db "ForwarderChain = %lX"0Dh0Ah
db "Name = %s"0Dh0Ah
db "FirstThunk = %lX"0
NameHeader db 0Dh0Ah"Hint Function"0Dh0Ah
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 GetModuleHandleNULL
invoke DialogBoxParam eax IDD_MAINDLGNULLaddr DlgProc 0
invoke ExitProcess 0

DlgProc proc hDlg:DWORD uMsg:DWORD wParam:DWORD lParam:DWORD
.if uMsg==WM_INITDIALOG
invoke SendDlgItemMessagehDlgIDC_EDITEM_SETLIMITTEXT00
.elseif uMsg==WM_CLOSE
invoke EndDialoghDlg0
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eaxwParam
.if ax==IDM_OPEN
invoke ShowImportFunctionshDlg
.else ; IDM_EXIT
invoke SendMessagehDlgWM_CLOSE00
.endif
.endif
.else
mov eaxFALSE
ret
.endif
mov eaxTRUE
ret
DlgProc endp

SEHHandler proc uses edx pExcept:DWORD pFrame:DWORD pContext:DWORD pDispatch:DWORD
mov edxpFrame
assume edx:ptr SEH
mov eaxpContext
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 eaxExceptionContinueExecution
ret
SEHHandler endp

ShowImportFunctions proc uses edi hDlg:DWORD
LOCAL seh:SEH
mov ofn.lStructSizeSIZEOF
ofn mov ofn.lpstrFilter OFFSET FilterString
mov ofn.lpstrFile OFFSET buffer
mov ofn.nMaxFile512
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_READONLY000
.if eax!=NULL
mov hMapping eax
invoke MapViewOfFilehMappingFILE_MAP_READ000
.if eax!=NULL
mov pMappingeax
assume fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandleroffset SEHHandler
mov seh.SafeOffsetoffset FinalExit
lea eaxseh
mov fs:[0] eax
mov seh.PrevEspesp
mov seh.PrevEbpebp
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 ValidPEFALSE
.endif
FinalExit:
push seh.PrevLink
pop fs:[0]
.if ValidPE==TRUE
invoke ShowTheFunctions hDlg edi
.else
invoke MessageBox0 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 CloseHandlehMapping
.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:DWORDpText:DWORD
invoke SendDlgItemMessagehDlgIDC_EDITEM_REPLACESEL0pText
invoke SendDlgItemMessagehDlgIDC_EDITEM_REPLACESEL0addr CRLF
invoke SendDlgItemMessagehDlgIDC_EDITEM_SETSEL-10
ret
AppendText endp

RVAToOffset PROC uses edi esi edx ecx pFileMap:DWORDRVA:DWORD
mov esipFileMap
assume esi:ptr IMAGE_DOS_HEADER
add esi[esi].e_lfanew
assume esi:ptr IMAGE_NT_HEADERS
mov ediRVA ; edi == RVA
mov edxesi
add edxsizeof IMAGE_NT_HEADERS
mov cx[esi].FileHeader.NumberOfSections
movzx ecxcx
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 edieax
mov eax[edx].PointerToRawData
add eaxedi ; eax == file offset
ret
.endif
.endif
add edxsizeof IMAGE_SECTION_HEADER
dec ecx
.endw
assume edx:nothing
assume esi:nothing
mov eaxedi
ret
RVAToOffset endp

ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD pNTHdr:DWORD
LOCAL temp[512]:BYTE
invoke SetDlgItemTexthDlgIDC_EDIT0
invoke AppendTexthDlgaddr buffer
mov edipNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
invoke RVAToOffsetpMappingedi
mov edieax
add edipMapping
assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
.while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)
invoke AppendTexthDlgaddr ImportDescriptor
invoke RVAToOffsetpMapping [edi].Name1
mov edxeax
add edxpMapping
invoke wsprintf addr temp addr IDTemplate [edi].OriginalFirstThunk[edi].TimeDateStamp[edi].ForwarderChainedx[edi].FirstThunk invoke AppendTexthDlgaddr temp
.if [edi].OriginalFirstThunk==0
mov esi[edi].FirstThunk
.else
mov esi[edi].OriginalFirstThunk
.endif
invoke RVAToOffsetpMappingesi
add eaxpMapping
mov esieax
invoke AppendTexthDlgaddr NameHeader
.while dword ptr [esi]!=0
test dword ptr [esi]IMAGE_ORDINAL_FLAG32
jnz ImportByOrdinal
invoke RVAToOffsetpMappingdword ptr [esi]
mov edxeax
add edxpMapping
assume edx:ptr IMAGE_IMPORT_BY_NAME
mov cx [edx].Hint
movzx ecxcx
invoke wsprintfaddr tempaddr NameTemplateecxaddr [edx].Name1
jmp ShowTheText
ImportByOrdinal:
mov edxdword ptr [esi]
and edx0FFFFh
invoke wsprintfaddr tempaddr OrdinalTemplateedx
ShowTheText:
invoke AppendTexthDlgaddr temp
add esi4
.endw
add edisizeof IMAGE_IMPORT_DESCRIPTOR
.endw
ret
ShowTheFunctions endp
end start

分析:
本例中,用戶點(diǎn)擊打開菜單顯示文件打開對(duì)話框,檢驗(yàn)文件的PE有效性后調(diào)用 ShowTheFunctions。

ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD pNTHdr:DWORD
LOCAL temp[512]:BYTE

保留512字節(jié)堆棧空間用于字符串操作。

invoke SetDlgItemTexthDlgIDC_EDIT0

清除編輯控件內(nèi)容。

invoke AppendTexthDlgaddr buffer

將PE文件名插入編輯控件。 AppendText 通過(guò)傳遞一個(gè) EM_REPLACESEL 消息以通知向編輯控件添加文本。然后它又向編輯控件發(fā)送一個(gè)設(shè)置了 wParam=-1和lParam=0的EM_SETSEL 消息,使光標(biāo)定位到文本末。

mov edipNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress

獲取import symbols的RVA。edi起初指向 PE header,以此我們可以定位到數(shù)據(jù)目錄數(shù)組的第二個(gè)數(shù)組元素來(lái)得到虛擬地址值。

invoke RVAToOffsetpMappingedi
mov edieax
add edipMapping

這兒對(duì)PE編程初學(xué)者來(lái)說(shuō)可能有點(diǎn)困難。在PE文件中大多數(shù)地址多是RVAs 而 RVAs只有當(dāng)PE文件被PE裝載器裝入內(nèi)存后才有意義。 本例中,我們直接將文件映射到內(nèi)存而不是通過(guò)PE裝載器載入,因此我們不能直接使用那些RVAs。必須先將那些RVAs轉(zhuǎn)換成文件偏移量,RVAToOffset函數(shù)就起到這個(gè)作用。 這里不準(zhǔn)備詳細(xì)分析。指出的是,它還將給定的RVA和PE文件所有節(jié)的始末RVA作比較(檢驗(yàn)RVA的有效性),然后通過(guò)IMAGE_SECTION_HEADER 結(jié)構(gòu)中的PointerToRawData域(當(dāng)然是所在節(jié)的那個(gè)PointerToRawData域啦)將RVA轉(zhuǎn)換成文件偏移量。
函數(shù)使用需要傳遞兩個(gè)參數(shù): 內(nèi)存映射文件指針和所要轉(zhuǎn)換的RVA。eax里返回文件偏移量。上面代碼中,我們必須將文件偏移量加上內(nèi)存映射文件指針以轉(zhuǎn)換成虛擬地址。是不是有點(diǎn)復(fù)雜? :)

assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
.while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)

edi現(xiàn)在指向第一個(gè) IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)。接下來(lái)我們遍歷整個(gè)結(jié)構(gòu)數(shù)組直到遇上一個(gè)全0結(jié)構(gòu),這就是數(shù)組末尾了。

invoke AppendTexthDlgaddr ImportDescriptor
invoke RVAToOffsetpMapping [edi].Name1
mov edxeax
add edxpMapping

我們要顯示當(dāng)前 IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)的值。Name1 不同于其他結(jié)構(gòu)成員,它含有指向相關(guān)dll名的RVA。因此必須先將其轉(zhuǎn)換成虛擬地址。

invoke wsprintf addr temp addr IDTemplate [edi].OriginalFirstThunk[edi].TimeDateStamp[edi].ForwarderChainedx[edi].FirstThunk invoke AppendTexthDlgaddr temp

顯示當(dāng)前 IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)的值。

.if [edi].OriginalFirstThunk==0
mov esi[edi].FirstThunk
.else
mov esi[edi].OriginalFirstThunk
.endif

接下來(lái)準(zhǔn)備遍歷 IMAGE_THUNK_DATA 數(shù)組。通常我們會(huì)選擇OriginalFirstThunk指向的那個(gè)數(shù)組,不過(guò),如果某些連接器錯(cuò)誤地將OriginalFirstThunk 置0,這可以通過(guò)檢查OriginalFirstThunk值是否為0判斷。這樣的話,只要選擇FirstThunk指向的數(shù)組了。

invoke RVAToOffsetpMappingesi
add eaxpMapping
mov esieax

同樣的,OriginalFirstThunk/FirstThunk值是一個(gè)RVA。必須將其轉(zhuǎn)換為虛擬地址。

invoke AppendTexthDlgaddr NameHeader
.while dword ptr [esi]!=0

現(xiàn)在我們準(zhǔn)備遍歷 IMAGE_THUNK_DATAs 數(shù)組以查找該DLL引入的函數(shù)名,直到遇上全0項(xiàng)。

test dword ptr [esi]IMAGE_ORDINAL_FLAG32
jnz ImportByOrdinal

第一件事是校驗(yàn)IMAGE_THUNK_DATA 是否含有IMAGE_ORDINAL_FLAG32標(biāo)記。檢查IMAGE_THUNK_DATA 的MSB是否為1,如果是1,則函數(shù)是通過(guò)序數(shù)引出的,所以不需要更進(jìn)一步處理了。直接從 IMAGE_THUNK_DATA 提取低字節(jié)獲得序數(shù),然后是下一個(gè)IMAGE_THUNK_DATA 雙字。

invoke RVAToOffsetpMappingdword ptr [esi]
mov edxeax
add edxpMapping
assume edx:ptr IMAGE_IMPORT_BY_NAME

如果IMAGE_THUNK_DATA 的MSB是0,那么它包含了IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)的RVA。需要先轉(zhuǎn)換為虛擬地址。

mov cx [edx].Hint
movzx ecxcx
invoke wsprintfaddr tempaddr NameTemplateecxaddr [edx].Name1
jmp ShowTheText

Hint 是字類型,所以先轉(zhuǎn)換為雙字后再傳遞給wsprintf,然后我們將hint和函數(shù)名都顯示到編輯控件中。

ImportByOrdinal:
mov edxdword ptr [esi]
and edx0FFFFh
invoke wsprintfaddr tempaddr OrdinalTemplateedx

在僅用序數(shù)引出函數(shù)的情況中,先清空高字再顯示序數(shù)。

ShowTheText:
invoke AppendTexthDlgaddr temp
add esi4

在編輯控件中插入相應(yīng)的函數(shù)名/序數(shù)后,跳轉(zhuǎn)到下個(gè) IMAGE_THUNK_DATA。

.endw
add edisizeof IMAGE_IMPORT_DESCRIPTOR

處理完當(dāng)前IMAGE_THUNK_DATA 數(shù)組里的所有雙字,跳轉(zhuǎn)到下個(gè)IMAGE_IMPORT_DESCRIPTOR 開始處理其他DLLs的引入函數(shù)了。

附錄:
讓我們?cè)賮?lái)討論一下bound import。當(dāng)PE裝載器裝入PE文件時(shí),檢查引入表并將相關(guān)DLLs映射到進(jìn)程地址空間。然后象我們這樣遍歷IMAGE_THUNK_DATA 數(shù)組并用引入函數(shù)的真實(shí)地址替換IMAGE_THUNK_DATAs 值。這一步需要很多時(shí)間。如果程序員能事先正確預(yù)測(cè)函數(shù)地址,PE裝載器就不用每次裝入PE文件時(shí)都去修正IMAGE_THUNK_DATAs 值了。Bound import就是這種思想的產(chǎn)物。
為了方便實(shí)現(xiàn),Microsoft出品的類似Visual Studio的編譯器多提供了bind.exe這樣的工具,由它檢查PE文件的引入表并用引入函數(shù)的真實(shí)地址替換IMAGE_THUNK_DATA 值。當(dāng)文件裝入時(shí),PE裝載器必定檢查地址的有效性,如果DLL版本不同于PE文件存放的相關(guān)信息,或則DLLs需要重定位,那么裝載器認(rèn)為原先計(jì)算的地址是無(wú)效的,它必定遍歷OriginalFirstThunk指向的數(shù)組以獲取引入函數(shù)新地址。
Bound import在本課中并非很重要,我們確省就是用到了OriginalFirstThunk。要了解更多信息可參見LUEVELSMEYER的pe.txt。

翻譯:iamgufeng [Iczelion's Win32 Assembly Homepage][LuoYunBin's Win32 ASM Page]

PE教程7: Export Table(引出表)
上一課我們已經(jīng)學(xué)習(xí)了動(dòng)態(tài)聯(lián)接中關(guān)于引入表那部分知識(shí),現(xiàn)在繼續(xù)另外一部分,那就是引出表。

下載 范例。

理論:
當(dāng)PE裝載器執(zhí)行一個(gè)程序,它將相關(guān)DLLs都裝入該進(jìn)程的地址空間。然后根據(jù)主程序的引入函數(shù)信息,查找相關(guān)DLLs中的真實(shí)函數(shù)地址來(lái)修正主程序。PE裝載器搜尋的是DLLs中的引出函數(shù)。

DLL/EXE要引出一個(gè)函數(shù)給其他DLL/EXE使用,有兩種實(shí)現(xiàn)方法: 通過(guò)函數(shù)名引出或者僅僅通過(guò)序數(shù)引出。比如某個(gè)DLL要引出名為"GetSysConfig"的函數(shù),如果它以函數(shù)名引出,那么其他DLLs/EXEs若要調(diào)用這個(gè)函數(shù),必須通過(guò)函數(shù)名,就是GetSysConfig。另外一個(gè)辦法就是通過(guò)序數(shù)引出。什么是序數(shù)呢? 序數(shù)是唯一指定DLL中某個(gè)函數(shù)的16位數(shù)字,在所指向的DLL里是獨(dú)一無(wú)二的。例如在上例中,DLL可以選擇通過(guò)序數(shù)引出,假設(shè)是16,那么其他DLLs/EXEs若要調(diào)用這個(gè)函數(shù)必須以該值作為GetProcAddress調(diào)用參數(shù)。這就是所謂的僅僅靠序數(shù)引出。

我們不提倡僅僅通過(guò)序數(shù)引出函數(shù)這種方法,這會(huì)帶來(lái)DLL維護(hù)上的問(wèn)題。一旦DLL升級(jí)/修改,程序員無(wú)法改變函數(shù)的序數(shù),否則調(diào)用該DLL的其他程序都將無(wú)法工作。

現(xiàn)在我們開始學(xué)習(xí)引出結(jié)構(gòu)。象引出表一樣,可以通過(guò)數(shù)據(jù)目錄找到引出表的位置。這兒,引出表是數(shù)據(jù)目錄的第一個(gè)成員,又可稱為IMAGE_EXPORT_DIRECTORY。該結(jié)構(gòu)中共有11 個(gè)成員,常用的列于下表。

Field Name Meaning
nName 模塊的真實(shí)名稱。本域是必須的,因?yàn)槲募赡軙?huì)改變。這種情況下,PE裝載器將使用這個(gè)內(nèi)部名字。
nBase 基數(shù),加上序數(shù)就是函數(shù)地址數(shù)組的索引值了。
NumberOfFunctions 模塊引出的函數(shù)/符號(hào)總數(shù)。
NumberOfNames 通過(guò)名字引出的函數(shù)/符號(hào)數(shù)目。該值不是模塊引出的函數(shù)/符號(hào)總數(shù),這是由上面的NumberOfFunctions給出。本域可以為0,表示模塊可能僅僅通過(guò)序數(shù)引出。如果模塊根本不引出任何函數(shù)/符號(hào),那么數(shù)據(jù)目錄中引出表的RVA為0。
AddressOfFunctions 模塊中有一個(gè)指向所有函數(shù)/符號(hào)的RVAs數(shù)組,本域就是指向該RVAs數(shù)組的RVA。簡(jiǎn)言之,模塊中所有函數(shù)的RVAs都保存在一個(gè)數(shù)組里,本域就指向這個(gè)數(shù)組的首地址。
AddressOfNames 類似上個(gè)域,模塊中有一個(gè)指向所有函數(shù)名的RVAs數(shù)組,本域就是指向該RVAs數(shù)組的RVA。
AddressOfNameOrdinals RVA,指向包含上述 AddressOfNames數(shù)組中相關(guān)函數(shù)之序數(shù)的16位數(shù)組。

上面也許無(wú)法讓您完全理解引出表,下面的簡(jiǎn)述將助您一臂之力。

引出表的設(shè)計(jì)是為了方便PE裝載器工作。首先,模塊必須保存所有引出函數(shù)的地址以供PE裝載器查詢。模塊將這些信息保存在AddressOfFunctions域指向的數(shù)組中,而數(shù)組元素?cái)?shù)目存放在NumberOfFunctions域中。 因此,如果模塊引出40個(gè)函數(shù),則AddressOfFunctions指向的數(shù)組必定有40個(gè)元素,而NumberOfFunctions值為40。現(xiàn)在如果有一些函數(shù)是通過(guò)名字引出的,那么模塊必定也在文件中保留了這些信息。這些 名字的RVAs存放在一數(shù)組中以供PE裝載器查詢。該數(shù)組由AddressOfNames指向,NumberOfNames包含名字?jǐn)?shù)目。考慮一下PE裝載器的工作機(jī)制,它知道函數(shù)名,并想以此獲取這些函數(shù)的地址。至今為止,模塊已有兩個(gè)模塊: 名字?jǐn)?shù)組和地址數(shù)組,但兩者之間還沒有聯(lián)系的紐帶。因此我們還需要一些聯(lián)系函數(shù)名及其地址的東東。PE參考指出使用到地址數(shù)組的索引作為聯(lián)接,因此PE裝載器在名字?jǐn)?shù)組中找到匹配名字的同時(shí),它也獲取了 指向地址表中對(duì)應(yīng)元素的索引。 而這些索引保存在由AddressOfNameOrdinals域指向的另一個(gè)數(shù)組(最后一個(gè))中。由于該數(shù)組是起了聯(lián)系名字和地址的作用,所以其元素?cái)?shù)目必定和名字?jǐn)?shù)組相同,比如,每個(gè)名字有且僅有一個(gè)相關(guān)地址,反過(guò)來(lái)則不一定: 每個(gè)地址可以有好幾個(gè)名字來(lái)對(duì)應(yīng)。因此我們給同一個(gè)地址取"別名"。為了起到連接作用,名字?jǐn)?shù)組和索引數(shù)組必須并行地成對(duì)使用,譬如,索引數(shù)組的第一個(gè)元素必定含有第一個(gè)名字的索引,以此類推。

AddressOfNames   AddressOfNameOrdinals
|   |
RVA of Name 1
RVA of Name 2
RVA of Name 3
RVA of Name 4
...
RVA of Name N
<-->
<-->
<-->
<-->
...
<-->
Index of Name 1
Index of Name 2
Index of Name 3
Index of Name 4
...
Index of Name N


下面舉一兩個(gè)例子說(shuō)明問(wèn)題。如果我們有了引出函數(shù)名并想以此獲取地址,可以這么做:

定位到PE header。
從數(shù)據(jù)目錄讀取引出表的虛擬地址。
定位引出表獲取名字?jǐn)?shù)目(NumberOfNames) 。
并行遍歷AddressOfNames和AddressOfNameOrdinals指向的數(shù)組匹配名字。如果在AddressOfNames 指向的數(shù)組中找到匹配名字,從AddressOfNameOrdinals 指向的數(shù)組中提取索引值。例如,若發(fā)現(xiàn)匹配名字的RVA存放在AddressOfNames 數(shù)組的第77個(gè)元素,那就提取AddressOfNameOrdinals數(shù)組的第77個(gè)元素作為索引值。如果遍歷完NumberOfNames 個(gè)元素,說(shuō)明當(dāng)前模塊沒有所要的名字。
從AddressOfNameOrdinals 數(shù)組提取的數(shù)值作為AddressOfFunctions 數(shù)組的索引。也就是說(shuō),如果值是5,就必須讀取AddressOfFunctions 數(shù)組的第5個(gè)元素,此值就是所要函數(shù)的RVA。
現(xiàn)在我們?cè)诎炎⒁饬D(zhuǎn)向IMAGE_EXPORT_DIRECTORY 結(jié)構(gòu)的nBase成員。您已經(jīng)知道AddressOfFunctions 數(shù)組包含了模塊中所有引出符號(hào)的地址。當(dāng)PE裝載器索引該數(shù)組查詢函數(shù)地址時(shí),讓我們?cè)O(shè)想這樣一種情況,如果程序員在.def文件中設(shè)定起始序數(shù)號(hào)為200,這意味著AddressOfFunctions 數(shù)組至少有200個(gè)元素,甚至這前面200個(gè)元素并沒使用,但它們必須存在,因?yàn)镻E裝載器這樣才能索引到正確的地址。這種方法很不好,所以又設(shè)計(jì)了nBase 域解決這個(gè)問(wèn)題。如果程序員指定起始序數(shù)號(hào)為200,nBase 值也就是200。當(dāng)PE裝載器讀取nBase域時(shí),它知道開始200個(gè)元素并不存在,這樣減掉一個(gè)nBase值后就可以正確地索引AddressOfFunctions 數(shù)組了。有了nBase,就節(jié)約了200個(gè)空元素。 注意nBase并不影響AddressOfNameOrdinals數(shù)組的值。盡管取名"AddressOfNameOrdinals",該數(shù)組實(shí)際包含的是指向AddressOfFunctions 數(shù)組的索引,而不是什么序數(shù)啦。

討論完nBase的作用,我們繼續(xù)下一個(gè)例子。
假設(shè)我們只有函數(shù)的序數(shù),那么怎樣獲取函數(shù)地址呢,可以這么做:

定位到PE header。
從數(shù)據(jù)目錄讀取引出表的虛擬地址。
定位引出表獲取nBase值。
減掉nBase值得到指向AddressOfFunctions 數(shù)組的索引。
將該值與NumberOfFunctions作比較,大于等于后者則序數(shù)無(wú)效。
通過(guò)上面的索引就可以獲取AddressOfFunctions 數(shù)組中的RVA了。
可以看出,從序數(shù)獲取函數(shù)地址比函數(shù)名快捷容易。不需要遍歷AddressOfNames 和 AddressOfNameOrdinals 這兩個(gè)數(shù)組。然而,綜合性能必須與模塊維護(hù)的簡(jiǎn)易程度作一平衡。

總之,如果想通過(guò)名字獲取函數(shù)地址,需要遍歷AddressOfNames 和 AddressOfNameOrdinals 這兩個(gè)數(shù)組。如果使用函數(shù)序數(shù),減掉nBase值后就可直接索引AddressOfFunctions 數(shù)組。

如果一函數(shù)通過(guò)名字引出,那在GetProcAddress中可以使用名字或序數(shù)。但函數(shù)僅由序數(shù)引出情況又怎樣呢? 現(xiàn)在就來(lái)看看。
"一個(gè)函數(shù)僅由序數(shù)引出"意味著函數(shù)在AddressOfNames 和 AddressOfNameOrdinals 數(shù)組中不存在相關(guān)項(xiàng)。記住兩個(gè)域,NumberOfFunctions 和 NumberOfNames。這兩個(gè)域可以清楚地顯示有時(shí)某些函數(shù)沒有名字的。函數(shù)數(shù)目至少等同于名字?jǐn)?shù)目,沒有名字的函數(shù)通過(guò)序數(shù)引出。比如,如果存在70個(gè)函數(shù)但AddressOfNames數(shù)組中只有40項(xiàng),這就意味著模塊中有30個(gè)函數(shù)是僅通過(guò)序數(shù)引出的。現(xiàn)在我們?cè)鯓诱页瞿切﹥H通過(guò)序數(shù)引出的函數(shù)呢?這不容易,必須通過(guò)排除法,比如,AddressOfFunctions 的數(shù)組項(xiàng)在AddressOfNameOrdinals 數(shù)組中不存在相關(guān)指向,這就說(shuō)明該函數(shù)RVA只通過(guò)序數(shù)引出。

示例:
本例類似上課的范例。然而,在顯示IMAGE_EXPORT_DIRECTORY 結(jié)構(gòu)一些成員信息的同時(shí),也列出了引出函數(shù)的RVAs,序數(shù)和名字。注意本例沒有列出僅由序數(shù)引出的函數(shù)。

.386
.model flatstdcall
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
ShowExportFunctions proto :DWORD
ShowTheFunctions proto :DWORD:DWORD
AppendText proto :DWORD:DWORD


SEH struct
PrevLink dd ?
CurrentHandler dd ?
SafeOffset dd ?
PrevEsp dd ?
PrevEbp dd ?
SEH ends

.data
AppName db "PE tutorial no.7"0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe *.dll)"0"*.exe;*.dll"0
db "All Files"0"*.*"00
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
NoExportTable db "No export information in this file"0
CRLF db 0Dh0Ah0
ExportTable db 0Dh0Ah"======[ IMAGE_EXPORT_DIRECTORY ]======"0Dh0Ah
db "Name of the module: %s"0Dh0Ah
db "nBase: %lu"0Dh0Ah
db "NumberOfFunctions: %lu"0Dh0Ah
db "NumberOfNames: %lu"0Dh0Ah
db "AddressOfFunctions: %lX"0Dh0Ah
db "AddressOfNames: %lX"0Dh0Ah
db "AddressOfNameOrdinals: %lX"0Dh0Ah0
Header db "RVA Ord. Name"0Dh0Ah
db "----------------------------------------------"0
template db "%lX %u %s"0

.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?

.code
start:
invoke GetModuleHandleNULL
invoke DialogBoxParam eax IDD_MAINDLGNULLaddr DlgProc 0
invoke ExitProcess 0

DlgProc proc hDlg:DWORD uMsg:DWORD wParam:DWORD lParam:DWORD
.if uMsg==WM_INITDIALOG
invoke SendDlgItemMessagehDlgIDC_EDITEM_SETLIMITTEXT00
.elseif uMsg==WM_CLOSE
invoke EndDialoghDlg0
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eaxwParam
.if ax==IDM_OPEN
invoke ShowExportFunctionshDlg
.else ; IDM_EXIT
invoke SendMessagehDlgWM_CLOSE00
.endif
.endif
.else
mov eaxFALSE
ret
.endif
mov eaxTRUE
ret
DlgProc endp

SEHHandler proc uses edx pExcept:DWORD pFrame:DWORD pContext:DWORD pDispatch:DWORD
mov edxpFrame
assume edx:ptr SEH
mov eaxpContext
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 eaxExceptionContinueExecution
ret
SEHHandler endp

ShowExportFunctions proc uses edi hDlg:DWORD
LOCAL seh:SEH
mov ofn.lStructSizeSIZEOF ofn
mov ofn.lpstrFilter OFFSET FilterString
mov ofn.lpstrFile OFFSET buffer
mov ofn.nMaxFile512
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_READONLY000
.if eax!=NULL
mov hMapping eax
invoke MapViewOfFilehMappingFILE_MAP_READ000
.if eax!=NULL
mov pMappingeax
assume fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandleroffset SEHHandler
mov seh.SafeOffsetoffset FinalExit
lea eaxseh
mov fs:[0] eax
mov seh.PrevEspesp
mov seh.PrevEbpebp
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 ValidPEFALSE
.endif
FinalExit:
push seh.PrevLink
pop fs:[0]
.if ValidPE==TRUE
invoke ShowTheFunctions hDlg edi
.else
invoke MessageBox0 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 CloseHandlehMapping
.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
ShowExportFunctions endp

AppendText proc hDlg:DWORDpText:DWORD
invoke SendDlgItemMessagehDlgIDC_EDITEM_REPLACESEL0pText
invoke SendDlgItemMessagehDlgIDC_EDITEM_REPLACESEL0addr CRLF
invoke SendDlgItemMessagehDlgIDC_EDITEM_SETSEL-10
ret
AppendText endp

RVAToFileMap PROC uses edi esi edx ecx pFileMap:DWORDRVA:DWORD
mov esipFileMap
assume esi:ptr IMAGE_DOS_HEADER
add esi[esi].e_lfanew
assume esi:ptr IMAGE_NT_HEADERS
mov ediRVA ; edi == RVA
mov edxesi
add edxsizeof IMAGE_NT_HEADERS
mov cx[esi].FileHeader.NumberOfSections
movzx ecxcx
assume edx:ptr IMAGE_SECTION_HEADER
.while ecx>0
.if edi>=[edx].VirtualAddress
mov eax[edx].VirtualAddress
add eax[edx].SizeOfRawData
.if edi<eax
mov eax[edx].VirtualAddress
sub edieax
mov eax[edx].PointerToRawData
add eaxedi
add eaxpFileMap
ret
.endif
.endif
add edxsizeof IMAGE_SECTION_HEADER
dec ecx
.endw
assume edx:nothing
assume esi:nothing
mov eaxedi
ret
RVAToFileMap endp

ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD pNTHdr:DWORD
LOCAL temp[512]:BYTE
LOCAL NumberOfNames:DWORD
LOCAL Base:DWORD

mov edipNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi [edi].OptionalHeader.DataDirectory.VirtualAddress
.if edi==0
invoke MessageBox0 addr NoExportTableaddr AppNameMB_OK+MB_ICONERROR
ret
.endif
invoke SetDlgItemTexthDlgIDC_EDIT0
invoke AppendTexthDlgaddr buffer
invoke RVAToFileMappMappingedi
mov edieax
assume edi:ptr IMAGE_EXPORT_DIRECTORY
mov eax[edi].NumberOfFunctions
invoke RVAToFileMap pMapping[edi].nName
invoke wsprintf addr tempaddr ExportTable eax [edi].nBase [edi].NumberOfFunctions [edi].NumberOfNames [edi].AddressOfFunctions [edi].AddressOfNames [edi].AddressOfNameOrdinals
invoke AppendTexthDlgaddr temp
invoke AppendTexthDlgaddr Header
push [edi].NumberOfNames
pop NumberOfNames
push [edi].nBase
pop Base
invoke RVAToFileMappMapping[edi].AddressOfNames
mov esieax
invoke RVAToFileMappMapping[edi].AddressOfNameOrdinals
mov ebxeax
invoke RVAToFileMappMapping[edi].AddressOfFunctions
mov edieax
.while NumberOfNames>0
invoke RVAToFileMappMappingdword ptr [esi]
mov dx[ebx]
movzx edxdx
mov ecxedx
shl edx2
add edxedi
add ecxBase
invoke wsprintf addr tempaddr templatedword ptr [edx]ecxeax
invoke AppendTexthDlgaddr temp
dec NumberOfNames
add esi4
add ebx2
.endw
ret
ShowTheFunctions endp
end start

分析:
mov edipNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi [edi].OptionalHeader.DataDirectory.VirtualAddress
.if edi==0
invoke MessageBox0 addr NoExportTableaddr AppNameMB_OK+MB_ICONERROR
ret
.endif

程序檢驗(yàn)PE有效性后,定位到數(shù)據(jù)目錄獲取引出表的虛擬地址。若該虛擬地址為0,則文件不含引出符號(hào)。

mov eax[edi].NumberOfFunctions
invoke RVAToFileMap pMapping[edi].nName
invoke wsprintf addr tempaddr ExportTable eax [edi].nBase [edi].NumberOfFunctions [edi].NumberOfNames [edi].AddressOfFunctions [edi].AddressOfNames [edi].AddressOfNameOrdinals
invoke AppendTexthDlgaddr temp

在編輯控件中顯示IMAGE_EXPORT_DIRECTORY 結(jié)構(gòu)的一些重要信息。

push [edi].NumberOfNames
pop NumberOfNames
push [edi].nBase
pop Base

由于我們要枚舉所有函數(shù)名,就要知道引出表里的名字?jǐn)?shù)目。nBase 在將AddressOfFunctions 數(shù)組索引轉(zhuǎn)換成序數(shù)時(shí)派到用場(chǎng)。

invoke RVAToFileMappMapping[edi].AddressOfNames
mov esieax
invoke RVAToFileMappMapping[edi].AddressOfNameOrdinals
mov ebxeax
invoke RVAToFileMappMapping[edi].AddressOfFunctions
mov edieax

將三個(gè)數(shù)組的地址相應(yīng)存放到esi,ebx,edi中。準(zhǔn)備開始訪問(wèn)。

.while NumberOfNames>0

直到所有名字都被處理完畢。

invoke RVAToFileMappMappingdword ptr [esi]

由于esi指向包含名字字符串RVAs的數(shù)組,所以[esi]含有當(dāng)前名字的RVA,需要將它轉(zhuǎn)換成虛擬地址,后面wsprintf要用的。

mov dx[ebx]
movzx edxdx
mov ecxedx
add ecxBase


ebx指向序數(shù)數(shù)組,值是字類型的。因此我們先要將其轉(zhuǎn)換成雙字,此時(shí)edx和ecx含有指向AddressOfFunctions 數(shù)組的索引。我們用edx作為索引值,而將ecx加上nBase得到函數(shù)的序數(shù)值。=

shl edx2
add edxedi

索引乘以4 (AddressOfFunctions 數(shù)組中每個(gè)元素都是4字節(jié)大小) 然后加上數(shù)組首地址,這樣edx指向的就是所要函數(shù)的RVA了。

invoke wsprintf addr tempaddr templatedword ptr [edx]ecxeax
invoke AppendTexthDlgaddr temp

在編輯控件中顯示函數(shù)的RVA 序數(shù) 和名字。

dec NumberOfNames
add esi4
add ebx2
.endw

修正計(jì)數(shù)器,AddressOfNames 和 AddressOfNameOrdinals 兩數(shù)組的當(dāng)前指針,繼續(xù)遍歷直到所有名字全都處理完畢。

翻譯:iamgufeng [Iczelion's Win32 Assembly Homepage][LuoYunBin's Win32 ASM Page]

(編輯:天命孤獨(dú))

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表

圖片精選

主站蜘蛛池模板: 欧美成人一区免费视频 | 天天色综合2 | 久久久久久久久久久久久国产精品 | 久久91亚洲精品久久91综合 | 精品无码久久久久久国产 | av国产免费 | 免费国产网站 | 91快色视频| 美女黄污视频 | 色污视频| 全黄裸片武则天一级第4季 偿还电影免费看 | 羞羞网站在线观看入口免费 | 久国久产久精永久网页 | 黄网站在线免费 | 日本欧美一区二区三区视频麻豆 | 中文字幕综合在线观看 | 亚洲视频欧美 | 国产福利不卡一区二区三区 | 精品视频一区二区三区四区 | 色中色在线播放 | av电影免费在线看 | gril hd| 国产精品aⅴ| 久久噜噜噜 | 国产一区二区三区色淫影院 | 九九热九九热 | 嗯~啊~弄嗯~啊h高潮视频 | 日本视频免费观看 | 欧美一级高潮 | 久久国产亚洲精品 | 日本一级黄色大片 | 久久视频精品 | 久久免费视频在线 | 久久亚洲线观看视频 | 久草在线免费资源站 | 国产精品视频一区二区三区四 | 国产精品一区二区在线 | 特级a欧美做爰片毛片 | 日韩毛片在线看 | 久久国产秒 | 欧美成人性生活片 |