亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 邏輯算法 > 正文

第一節 PE文件格式

2019-09-10 09:02:21
字體:
來源:轉載
供稿:網友

 
第一節 PE文件格式

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

教程就讓我們瀏覽一下 PE文件格式的概要。

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

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

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

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

如果我們將PE文件格式視為一邏輯磁盤,PE header是boot扇區而sections是各種文件,但我們仍缺乏足夠信息來定位磁盤上的不同文件,譬如,什么是PE文件格式中等價于目錄的東東?別急,那就是 PE header 接下來的數組結構section table(節表)。 每個結構包含對應節的屬性、文件偏移量、虛擬偏移量等。如果PE文件里有5個節,那么此結構數組內就有5個成員。因此,我們便可以把節表視為邏輯磁盤中的根目錄,每個數組成員等價于根目錄中目錄項。

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

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

你應該下載 LUEVELSMEYER的《PE文件格式》。 該文的描述相當詳細,可用作案頭的參考手冊。

PE教程2: 檢驗PE文件的有效性
教程中我們將學習如何檢測給定文件是一有效PE文件。
下載 范例

理論:
如何才能校驗指定文件是否為一有效PE文件呢? 這個問題很難回答,完全取決于想要的精準程度。您可以檢驗PE文件格式里的各個數據結構,或者僅校驗一些關鍵數據結構。大多數情況下,沒有必要校驗文件里的每一個數據結構,只要一些關鍵數據結構有效,我們就認為是有效的PE文件了。下面我們就來實現前面的假設。

我們要驗證的重要數據結構就是 PE header。從編程角度看,PE header 實際就是一個 IMAGE_NT_HEADERS 結構。定義如下:

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)。 本域為PE標記,我們可以此識別給定文件是否為有效PE文件。
FileHeader 該結構域包含了關于PE文件物理分布的信息, 比如節數目、文件執行機器等。
OptionalHeader 該結構域包含了關于PE文件邏輯分布的信息,雖然域名有"可選"字樣,但實際上本結構總是存在的。

我們目的很明確。如果IMAGE_NT_HEADERS的signature域值等于"PE/0/0",那么就是有效的PE文件。實際上,為了比較方便,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

接下來的問題是: 如何定位 PE header? 答案很簡單: DOS MZ header 已經包含了指向 PE header 的文件偏移量。DOS MZ header 又定義成結構IMAGE_DOS_HEADER 。查詢windows.inc,我們知道 IMAGE_DOS_HEADER 結構的e_lfanew成員就是指向 PE header 的文件偏移量。

現在將所有步驟總結如下:

首先檢驗文件頭部第一個字的值是否等于 IMAGE_DOS_SIGNATURE,是則 DOS MZ header 有效。
一旦證明文件的 DOS header 有效后,就可用e_lfanew來定位 PE header 了。
比較 PE header 的第一個字的值是否等于IMAGE_NT_HEADER。如果前后兩個值都匹配,那我們就認為該文件是一個有效的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

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

程序調用打開文件通用對話框,用戶選定執行文件后,程序便打開文件并映射到內存。并在有效性檢驗前建立一 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

一開始就假設寄存器 fs為空(assume fs:nothing)。 記住這一步不能省卻,因為MASM假設fs寄存器為ERROR。接下來保存 Windows使用的舊SEH處理函數地址到我們自己定義的結構中,同時保存我們的SEH處理函數地址和異常處理時的執行恢復地址,這樣一旦錯誤發生就能由異常處理函數安全地恢復執行了。同時還保存當前esp及ebp的值,以便我們的SEH處理函數將堆?;謴偷秸顟B。

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

成功建立SEH后繼續校驗工作。置目標文件的首字節地址給edi,使其指向DOS header的首字節。為便于比較,我們告訴編譯器可以假定edi正指向IMAGE_DOS_HEADER結構(事實亦是如此)。然后比較DOS header的首字是否等于字符串"MZ",這里利用了windows.inc中定義的IMAGE_DOS_SIGNATURE常量。若比較成功,繼續轉到PE header,否則設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在文件中相對文件首部的偏移量。edi加上該值正好定位到PE header的首字節。這兒可能會出錯,如果文件不是PE文件,e_lfanew值就不正確,加上該值作為指針就可能導致異常。若不用SEH,我們必須校驗e_lfanew值是否超出文件尺寸,這不是一個好辦法。如果一切OK,我們就比較PE header的首字是否是字符串"PE"。這里在此用到了常量IMAGE_NT_SIGNATURE,相等則認為是有效的PE文件。
如果e_lfanew的值不正確導致異常,我們的SEH處理函數就得到執行控制權,簡單恢復堆棧指針和基棧指針后,就根據safeoffset的值恢復執行到FinalExit標簽處。

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

上述代碼簡單明確,根據ValidPE的值顯示相應信息。

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(文件頭)部分。

至此,我們已經學到了哪些東東,先簡要回顧一下:

DOS MZ header 又命名為IMAGE_DOS_HEADER.。其中只有兩個域比較重要: e_magic 包含字符串"MZ",e_lfanew 包含PE header在文件中的偏移量。
比較e_magic 是否為IMAGE_DOS_SIGNATURE以驗證是否是有效的DOS header。比對符合則認為文件擁有一個有效的DOS header。
為了定位PE header,移動文件指針到e_lfanew所指向的偏移。
PE header的第一個雙字包含字符串"PE/0/0"。該雙字與IMAGE_NT_SIGNATURE比對,符合則認為PE header有效。
本課我們繼續討關于 PE header 的知識。 PE header 的正式命名是 IMAGE_NT_HEADERS。再來回憶一下這個結構。

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

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

最有趣的東東在 OptionalHeader 里。不過,FileHeader 里的一些域也很重要。本課我們將學習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 該文件運行所要求的CPU。對于Intel平臺,該值是IMAGE_FILE_MACHINE_I386 (14Ch)。我們嘗試了LUEVELSMEYER的pe.txt聲明的14Dh和14Eh,但Windows不能正確執行??雌饋恚私钩绦驁绦兄?,本域對我們來說用處不大。
NumberOfSections 文件的節數目。如果我們要在文件中增加或刪除一個節,就需要修改這個值。
TimeDateStamp 文件創建日期和時間。我們不感興趣。
PointerToSymbolTable 用于調試。
NumberOfSymbols 用于調試。
SizeOfOptionalHeader 指示緊隨本結構之后的 OptionalHeader 結構大小,必須為有效值。
Characteristics 關于文件信息的標記,比如文件是exe還是dll。

簡言之,只有三個域對我們有一些用: Machine NumberOfSections 和 Characteristics。通常不會改變 Machine 和Characteristics 的值,但如果要遍歷節表就得使用 NumberOfSections。
為了更好闡述 NumberOfSections 的用處,這里簡要介紹一下節表。

節表是一個結構數組,每個結構包含一個節的信息。因此若有3個節,數組就有3個成員。 我們需要NumberOfSections值來了解該數組中到底有幾個成員。 也許您會想檢測結構中的全0成員起到同樣效果。Windows確實采用了這種方法。為了證明這一點,可以增加NumberOfSections的值,Windows仍然可以正常執行文件。據我們的觀察,Windows讀取NumberOfSections的值然后檢查節表里的每個結構,如果找到一個全0結構就結束搜索,否則一直處理完NumberOfSections指定數目的結構。 為什么我們不能忽略NumberOfSections的值? 有幾個原因。PE說明中沒有指定節表必須以全0結構結束。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
我們已經學習了關于 DOS header 和 PE header 中部分成員的知識。這里是 PE header 中最后、最大或許也是最重要的成員,optional header。

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

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

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

MajorSubsystemVersion
MinorSubsystemVersion win32子系統版本。若PE文件是專門為Win32設計的,該子系統版本必定是4.0否則對話框不會有3維立體感。
SizeOfImage 內存中整個PE映像體的尺寸。它是所有頭和節經過節對齊處理后的大小。
SizeOfHeaders 所有頭+節表的大小,也就等于文件尺寸減去文件中所有節的尺寸??梢砸源酥底鳛镻E文件第一節的文件偏移量。
Subsystem NT用來識別PE文件屬于哪個子系統。 對于大多數Win32程序,只有兩類值: Windows GUI 和 Windows CUI (控制臺)。
DataDirectory 一IMAGE_DATA_DIRECTORY 結構數組。每個結構給出一個重要數據結構的RVA,比如引入地址表等。

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

PE教程5: Section Table(節表)
下載 范例。

理論:
到本課為止,我們已經學了許多關于 DOS header 和 PE header 的知識。接下來就該輪到 section table(節表)了。節表其實就是緊挨著 PE header 的一結構數組。該數組成員的數目由 file header (IMAGE_FILE_HEADER) 結構中 NumberOfSections 域的域值來決定。節表結構又命名為 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

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

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

現在我們已知曉 IMAGE_SECTION_HEADER 結構,再來模擬一下 PE裝載器的工作吧:

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

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

.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的代碼,校驗PE文件的有效性后,繼續調用函數ShowSectionInfo顯示各節信息。

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文件數據的指針。首先,將指向DOS header地址的pMapping賦給edi,再加上e_lfanew域值等于PE header的地址。

mov ax[edi].FileHeader.NumberOfSections
mov NumberOfSectionsax

因為我們要遍歷節表,所以必須先獲取文件的節數目。這就得靠file header里的NumberOfSections域了,切記這是個word域。

add edisizeof IMAGE_NT_HEADERS

現在edi正指向PE header的起始地址,加上PE header結構大小后恰好指向節表了。

invoke DialogBoxParam hInstance IDD_SECTIONTABLENULL addr DlgProc edi

調用 DialogBoxParam 顯示列表對話框,注意我們已將節表地址作為最后一個參數傳遞過去了,該值可從WM_INITDIALOG 消息的lParam參數中提取。

在對話框過程里我們響應WM_INITDIALOG消息,將lParam值 (節表地址)存入esi,節數目賦給edi并設置列表控件。萬事俱備后,進入循環將各節信息插入到列表控件中,這部分相當簡單。 .while edi>0
mov lvi.iSubItem0

字符串置入第一列。

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

要顯示節名,當然要將其轉換為ASCIIZ字符串先。

invoke SendDlgItemMessagehDlgIDC_SECTIONLISTLVM_INSERTITEM0addr lvi

然后顯示第一列。
繼續我們偉大的工程,顯示完本節中最后一個欲呈現的值后,立馬下一個結構。 dec edi
add esi sizeof IMAGE_SECTION_HEADER
.endw

每處理完一節就遞減edi,然后將esi加上IMAGE_SECTION_HEADER 結構大小,使其指向下一個IMAGE_SECTION_HEADER 結構。

遍歷節表的步驟:

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

PE教程6: Import Table(引入表)
本課我們將學習引入表。先警告一下,對于不熟悉引入表的讀者來說,這是一堂又長又難的課,所以需要多讀幾遍,最好再打開調試器來好好分析相關結構。各位,努力?。?/P>

下載范例。

理論:
首先,您得了解什么是引入函數。一個引入函數是被某模塊調用的但又不在調用者模塊中的函數,因而命名為"import(引入)"。引入函數實際位于一個或者更多的DLL里。調用者模塊里只保留一些函數信息,包括函數名及其駐留的DLL名?,F在,我們怎樣才能找到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_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


現在您應該明白我的意思。不要被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
|
      |
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數組始終不會改變,所以若還反過頭來查找引入函數名,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結尾)?,F在我們已遍歷完一個DLL的引入函數,接下去處理下一個DLL。
即跳轉到下一個 IMAGE_IMPORT_DESCRIPTOR 并處理之,如此這般循環直到數組見底。(IMAGE_IMPORT_DESCRIPTOR 數組以一個全0域元素結尾)。
示例:
本例程打開一PE文件,將所有引入函數名讀入一編輯控件,同時顯示 IMAGE_IMPORT_DESCRIPTOR 結構各域值。

.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

分析:
本例中,用戶點擊打開菜單顯示文件打開對話框,檢驗文件的PE有效性后調用 ShowTheFunctions。

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

保留512字節堆??臻g用于字符串操作。

invoke SetDlgItemTexthDlgIDC_EDIT0

清除編輯控件內容。

invoke AppendTexthDlgaddr buffer

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

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

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

invoke RVAToOffsetpMappingedi
mov edieax
add edipMapping

這兒對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 AppendTexthDlgaddr ImportDescriptor
invoke RVAToOffsetpMapping [edi].Name1
mov edxeax
add edxpMapping

我們要顯示當前 IMAGE_IMPORT_DESCRIPTOR 結構的值。Name1 不同于其他結構成員,它含有指向相關dll名的RVA。因此必須先將其轉換成虛擬地址。

invoke wsprintf addr temp addr IDTemplate [edi].OriginalFirstThunk[edi].TimeDateStamp[edi].ForwarderChainedx[edi].FirstThunk invoke AppendTexthDlgaddr 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 RVAToOffsetpMappingesi
add eaxpMapping
mov esieax

同樣的,OriginalFirstThunk/FirstThunk值是一個RVA。必須將其轉換為虛擬地址。

invoke AppendTexthDlgaddr 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 RVAToOffsetpMappingdword ptr [esi]
mov edxeax
add edxpMapping
assume edx:ptr IMAGE_IMPORT_BY_NAME

如果IMAGE_THUNK_DATA 的MSB是0,那么它包含了IMAGE_IMPORT_BY_NAME 結構的RVA。需要先轉換為虛擬地址。

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

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

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

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

ShowTheText:
invoke AppendTexthDlgaddr temp
add esi4

在編輯控件中插入相應的函數名/序數后,跳轉到下個 IMAGE_THUNK_DATA。

.endw
add edisizeof 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。要了解更多信息可參見LUEVELSMEYER的pe.txt。

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

PE教程7: Export Table(引出表)
上一課我們已經學習了動態聯接中關于引入表那部分知識,現在繼續另外一部分,那就是引出表。

下載 范例。

理論:
當PE裝載器執行一個程序,它將相關DLLs都裝入該進程的地址空間。然后根據主程序的引入函數信息,查找相關DLLs中的真實函數地址來修正主程序。PE裝載器搜尋的是DLLs中的引出函數。

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

我們不提倡僅僅通過序數引出函數這種方法,這會帶來DLL維護上的問題。一旦DLL升級/修改,程序員無法改變函數的序數,否則調用該DLL的其他程序都將無法工作。

現在我們開始學習引出結構。象引出表一樣,可以通過數據目錄找到引出表的位置。這兒,引出表是數據目錄的第一個成員,又可稱為IMAGE_EXPORT_DIRECTORY。該結構中共有11 個成員,常用的列于下表。

Field Name Meaning
nName 模塊的真實名稱。本域是必須的,因為文件名可能會改變。這種情況下,PE裝載器將使用這個內部名字。
nBase 基數,加上序數就是函數地址數組的索引值了。
NumberOfFunctions 模塊引出的函數/符號總數。
NumberOfNames 通過名字引出的函數/符號數目。該值不是模塊引出的函數/符號總數,這是由上面的NumberOfFunctions給出。本域可以為0,表示模塊可能僅僅通過序數引出。如果模塊根本不引出任何函數/符號,那么數據目錄中引出表的RVA為0。
AddressOfFunctions 模塊中有一個指向所有函數/符號的RVAs數組,本域就是指向該RVAs數組的RVA。簡言之,模塊中所有函數的RVAs都保存在一個數組里,本域就指向這個數組的首地址。
AddressOfNames 類似上個域,模塊中有一個指向所有函數名的RVAs數組,本域就是指向該RVAs數組的RVA。
AddressOfNameOrdinals RVA,指向包含上述 AddressOfNames數組中相關函數之序數的16位數組。

上面也許無法讓您完全理解引出表,下面的簡述將助您一臂之力。

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

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


下面舉一兩個例子說明問題。如果我們有了引出函數名并想以此獲取地址,可以這么做:

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

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

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

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

如果一函數通過名字引出,那在GetProcAddress中可以使用名字或序數。但函數僅由序數引出情況又怎樣呢? 現在就來看看。
"一個函數僅由序數引出"意味著函數在AddressOfNames 和 AddressOfNameOrdinals 數組中不存在相關項。記住兩個域,NumberOfFunctions 和 NumberOfNames。這兩個域可以清楚地顯示有時某些函數沒有名字的。函數數目至少等同于名字數目,沒有名字的函數通過序數引出。比如,如果存在70個函數但AddressOfNames數組中只有40項,這就意味著模塊中有30個函數是僅通過序數引出的。現在我們怎樣找出那些僅通過序數引出的函數呢?這不容易,必須通過排除法,比如,AddressOfFunctions 的數組項在AddressOfNameOrdinals 數組中不存在相關指向,這就說明該函數RVA只通過序數引出。

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

.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

程序檢驗PE有效性后,定位到數據目錄獲取引出表的虛擬地址。若該虛擬地址為0,則文件不含引出符號。

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 結構的一些重要信息。

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

由于我們要枚舉所有函數名,就要知道引出表里的名字數目。nBase 在將AddressOfFunctions 數組索引轉換成序數時派到用場。

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

將三個數組的地址相應存放到esi,ebx,edi中。準備開始訪問。

.while NumberOfNames>0

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

invoke RVAToFileMappMappingdword ptr [esi]

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

mov dx[ebx]
movzx edxdx
mov ecxedx
add ecxBase


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

shl edx2
add edxedi

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

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

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

dec NumberOfNames
add esi4
add ebx2
.endw

修正計數器,AddressOfNames 和 AddressOfNameOrdinals 兩數組的當前指針,繼續遍歷直到所有名字全都處理完畢。

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

(編輯:天命孤獨)

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美精品www| 亚洲免费伊人电影在线观看av| 欧美黑人性生活视频| 伊人亚洲福利一区二区三区| 日韩精品丝袜在线| 久久久极品av| 欧美一级黑人aaaaaaa做受| 色综合91久久精品中文字幕| 一区二区三区四区在线观看视频| 色妞一区二区三区| 日韩精品免费观看| 91性高湖久久久久久久久_久久99| 国产精品精品久久久久久| 亚洲精品在线观看www| 亚洲色图35p| 成人在线视频网站| 国产成人精品一区二区三区| 91成人在线视频| 国产一区二区三区高清在线观看| 高清欧美电影在线| 欧美中文字幕在线观看| 精品av在线播放| 精品无人国产偷自产在线| 亚洲综合精品一区二区| 亚洲国产精品999| 国产亚洲一级高清| 日韩av成人在线观看| 欧美激情区在线播放| 色阁综合伊人av| 久久国产精品久久久久久久久久| 国产精品久久久久久av福利| 这里只有精品丝袜| 亚洲国产精品久久91精品| 深夜成人在线观看| 精品少妇v888av| 成人精品一区二区三区电影免费| 亚洲全黄一级网站| 亚洲国产小视频在线观看| 成人免费福利视频| 国产视频在线观看一区二区| 亚洲a级在线观看| 欧美视频在线视频| 欧美日韩中国免费专区在线看| 日韩视频在线观看免费| 亚洲欧美日韩天堂| 操日韩av在线电影| 欧美性xxxxx极品娇小| 亚洲欧洲黄色网| 色综合视频一区中文字幕| 美女扒开尿口让男人操亚洲视频网站| 亚洲www永久成人夜色| 国内精品久久久久影院 日本资源| 欧美在线视频网站| 97成人精品区在线播放| 欧美日韩亚洲91| 在线播放国产一区二区三区| 全亚洲最色的网站在线观看| 中文字幕一区二区三区电影| 色综合久久中文字幕综合网小说| 国产精品国产自产拍高清av水多| 91av在线播放| 欧美日韩在线观看视频小说| 欧美国产一区二区三区| 青青a在线精品免费观看| 亚洲国产福利在线| 精品亚洲一区二区三区四区五区| 精品亚洲一区二区三区| 国产精品视频免费观看www| www.国产精品一二区| 九九热最新视频//这里只有精品| 91情侣偷在线精品国产| 国产精品男人的天堂| 最新中文字幕亚洲| 国产精品一区二区三| 日本午夜在线亚洲.国产| 色噜噜狠狠狠综合曰曰曰88av| 中文字幕日韩电影| 68精品国产免费久久久久久婷婷| 欧美视频一区二区三区…| 欧美视频一二三| 久久91亚洲人成电影网站| 国产精品高清在线| 日韩小视频在线观看| 欧美视频一区二区三区…| 91精品久久久久久久久久另类| 黑人极品videos精品欧美裸| 91免费看国产| 影音先锋日韩有码| 久久91亚洲精品中文字幕| 91国在线精品国内播放| 欧美性高潮床叫视频| 中文字幕亚洲综合久久筱田步美| 97国产精品人人爽人人做| 日韩精品高清视频| 中文字幕日韩免费视频| 色av中文字幕一区| 一区二区三区四区精品| 日韩成人小视频| 欧美成人免费一级人片100| 亚洲精选中文字幕| 亚洲综合大片69999| 97在线观看视频国产| 91亚洲精品在线| 亚洲美女自拍视频| 中文字幕欧美日韩va免费视频| 日韩在线视频网站| 亚洲影院色无极综合| 久久综合色影院| 亚洲欧洲国产伦综合| 国产亚洲精品激情久久| 亚洲人成自拍网站| 亚洲福利在线看| 欧美国产激情18| 精品免费在线观看| 97精品久久久中文字幕免费| 7m精品福利视频导航| 91色视频在线导航| 亚洲欧美自拍一区| 91免费看国产| 欧美性精品220| 色婷婷**av毛片一区| 国产精品久久久久久久久久久久久久| 精品久久久久久久久久| 亚洲欧美国产va在线影院| 亚洲综合色av| 欧美片一区二区三区| 美女视频黄免费的亚洲男人天堂| 久久久久久久久久久久av| 精品久久香蕉国产线看观看gif| 欧美在线一级视频| 欧美电影在线观看高清| 亚洲精品久久久久久下一站| 亚洲一区美女视频在线观看免费| 日韩第一页在线| 最近2019好看的中文字幕免费| 中文字幕亚洲二区| 日韩成人中文字幕在线观看| 久久伊人色综合| 国产91对白在线播放| 成人欧美一区二区三区黑人| 精品欧美国产一区二区三区| 欧美电影免费观看| 亚洲跨种族黑人xxx| 亚洲热线99精品视频| 欧美做爰性生交视频| 91国产中文字幕| 欧美精品videossex88| 欧美激情一级精品国产| 国内成人精品视频| 精品在线小视频| 中文在线资源观看视频网站免费不卡| 欧美日韩国产限制| 国内精品国产三级国产在线专| 国产精品吴梦梦| 国产成+人+综合+亚洲欧美丁香花| 最近2019好看的中文字幕免费| 亚洲国产成人在线视频| 欧美综合国产精品久久丁香| 欧美高清不卡在线| 岛国av午夜精品| 色噜噜亚洲精品中文字幕| 欧美刺激性大交免费视频| 欧洲成人免费视频| 国产精品久久久久久久久|