PE 格式详解
PE文件是Win32的原生文件格式.每一個(gè)Win32可執(zhí)行文件都遵循PE文件格式.對PE文件格式的了解可以加深你對Win32系統(tǒng)的深入理解.
一、 基本結(jié)構(gòu)。
?
上圖便是PE文件的基本結(jié)構(gòu)。(注意:DOS MZ Header和部分PE header的大小是不變的;DOS stub部分的大小是可變的。)
一個(gè)PE文件至少需要兩個(gè)Section,一個(gè)是存放代碼,一個(gè)存放數(shù)據(jù)。NT上的PE文件基本上有9個(gè)預(yù)定義的Section。分別是:.text, .bss, .rdata, .data, .rsrc, .edata, .idata, .pdata, 和 .debug。一些PE文件中只需要其中的一部分Section.以下是通常的分類:
l 執(zhí)行代碼Section , 通常命名為: .text (MS) or CODE (Borland)
l 數(shù)據(jù)Section, 通常命名為:.data, .rdata, 或 .bss(MS) 或 DATA(Borland).
l 資源Section, 通常命名為:.edata
l 輸入數(shù)據(jù)Section, 通常命名為:.idata
l 調(diào)試信息Section,通常命名為:.debug
這些只是命名方式,便于識別。通常與系統(tǒng)并無直接關(guān)系。通常,一個(gè)PE文件在磁盤上的映像跟內(nèi)存中的基本一致。但并不是完全的拷貝。Windows加載器會決定加載哪些部分,哪些部分不需要加載。而且由于磁盤對齊與內(nèi)存對齊的不一致,加載到內(nèi)存的PE文件與磁盤上的PE文件各個(gè)部分的分布都會有差異。
當(dāng)一個(gè)PE文件被加載到內(nèi)存后,便是我們常說的模塊(Module),其起始地址就是所謂的HModule.
二、 DOS頭結(jié)構(gòu)。
所有的PE文件都是以一個(gè)64字節(jié)的DOS頭開始。這個(gè)DOS頭只是為了兼容早期的DOS操作系統(tǒng)。這里不做詳細(xì)講解。只需要了解一下其中幾個(gè)有用的數(shù)據(jù)。
1. e_magic:DOS頭的標(biāo)識,為4Dh和5Ah。分別為字母MZ。
2. e_lfanew:一個(gè)雙字?jǐn)?shù)據(jù),為PE頭的離文件頭部的偏移量。Windows加載器通過它可以跳過DOS Stub部分直接找到PE頭。
3. DOS頭后跟一個(gè)DOS Stub數(shù)據(jù),是鏈接器鏈接執(zhí)行文件的時(shí)候加入的部分?jǐn)?shù)據(jù),一般是“This program must be run under Microsoft Windows”。這個(gè)可以通過修改鏈接器的設(shè)置來修改成自己定義的數(shù)據(jù)。
三、 PE頭結(jié)構(gòu)。
PE頭的數(shù)據(jù)結(jié)構(gòu)被定義為IMAGE_NT_HEADERS。包含三部分:
1. Signature:PE頭的標(biāo)識。雙字結(jié)構(gòu)。為50h, 45h, 00h, 00h. 即“PE”。
2. FileHeader:20字節(jié)的數(shù)據(jù)。包含了文件的物理層信息及文件屬性。
這里主要注意三項(xiàng)。
l NumberOfSections:定義PE文件Section的個(gè)數(shù)。如果對PE文件新增或刪除Section的話,一定要記的修改此域。
l SizeOfOptionalHeader:定義OptionHeader結(jié)構(gòu)的大小。
l Characteristics:主要用來標(biāo)識當(dāng)前的PE文件是執(zhí)行文件還是DLL。其各位都有具體的含義。
數(shù)據(jù)位
Windows.inc的預(yù)定義
為1時(shí)的含義
0
IMAGE_FILE_RELOCS_STRIPPED
文件中不存在重定位信息
1
IMAGE_FILE_EXECUTABLE_IMAGE
文件是可執(zhí)行的
2
IMAGE_FILE_LINE_NUMS_STRIPPED
不存在行信息
3
IMAGE_FILE_LOCAL_SYMS_STRIPPED
不存在符號信息
7
IMAGE_FILE_BYTES_REVERSED_LO
小尾方式
8
IMAGE_FILE_32BIT_MACHINE
只在32位平臺運(yùn)行
9
IMAGE_FILE_DEBUG_STRIPPED
不包含調(diào)試信息
10
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP
不能從可移動盤運(yùn)行
11
IMAGE_FILE_NET_RUN_FROM_SWAP
不能從網(wǎng)絡(luò)運(yùn)行
12
IMAGE_FILE_SYSTEM
系統(tǒng)文件。不能直接運(yùn)行
13
IMAGE_FILE_DLL
DLL文件
14
IMAGE_FILE_UP_SYSTEM_ONLY
文件不能在多處理器上運(yùn)行
15
IMAGE_FILE_BYTES_REVERSED_HI
大尾方式
3. OptionalHeader:總共224個(gè)字節(jié)。最后128個(gè)字節(jié)為數(shù)據(jù)目錄(Data Directory)。
以下是字段的說明:
l AddressOfEntryPoint:程序入口點(diǎn)地址。但加載器要運(yùn)行加載的PE文件時(shí)要執(zhí)行的第一個(gè)指令的地址。它是一個(gè)RVA(相對虛擬地址)地址。一些對PE文件插入代碼的程序就是修改此處的地址為要運(yùn)行的代碼,然后再跳轉(zhuǎn)回此處原來的地址。
l ImageBase:PE文件被加載到內(nèi)存的期望的基地址。對于EXE文件,通常加載后的地址就期望的地址。但是DLL卻可能是其他的。因?yàn)槿绻@個(gè)地址被占,系統(tǒng)就會重新分配一塊新的內(nèi)存,同時(shí)會修改此處加載后的地址。EXE文件通常是400000h.
l SectionAlignment:每一個(gè)Section的內(nèi)存對齊粒度。比如:此值為4096(1000h),那么每一個(gè)Section的起始地址都應(yīng)該是4096(1000h)的整數(shù)倍。如果第一個(gè)Section的地址是401000h,大小為100個(gè)字節(jié)。那么下一個(gè)Section的起始地址為402000h.。兩個(gè)Section之間的空間大部分是空的,未用的。
l FileAlignment:每一個(gè)Section的磁盤對齊粒度。比如,此值為512(200h),那么每一個(gè)Section在文件內(nèi)的偏移位置都是512(200h)的整數(shù)倍。與SectionAlignment同理。
l SizeOfImage:PE文件在內(nèi)存空間整個(gè)映像的大小。包含所有的頭及按SectinAlignment對齊的所有的Section。
l SizeOfHeaders:所有的頭加上Section表的大小。也就是文件大小減去文件中所有Section的大小??梢杂眠@個(gè)值獲取PE文件中第一Section的位置。
l DataDiretory:16個(gè)IMAGE_DATA_DIRECTORY結(jié)構(gòu)的數(shù)組。每一個(gè)成員都對應(yīng)一個(gè)重要的數(shù)據(jù)結(jié)構(gòu),比如輸入表,輸出表等。
?
有兩個(gè)地方需要注意:
l 如果PE header里的最后兩個(gè)字段被賦予一個(gè)偽造的值的話,比如:
n LoaderFlags = ABDBFFFFh (其默認(rèn)值為0)
n NumberOfRvaAndSizes = DFFDEEEEh (其默認(rèn)值為10h)
一些調(diào)試工具或反編譯工具會認(rèn)為這個(gè)PE文件是損壞的。有的會直接執(zhí)行,如果是病毒的話,就會被直接感染;有的則會重啟工具。所以最好在查看調(diào)試一個(gè)PE文件前,先看一下這里的取值是否被人賦予一個(gè)偽造的很大的值。如果是的話,先修改成默認(rèn)的值。
l 有人可能注意到在一些PE文件(MS的鏈接器鏈接的PE文件)的DOS Stub部分跟PE header部分之間存在一部分垃圾數(shù)據(jù)。標(biāo)識為其倒數(shù)第二非0的雙字節(jié)是一個(gè)“Rich ”。這部分?jǐn)?shù)據(jù)包含了一些加密數(shù)據(jù),來標(biāo)識編譯這個(gè)PE文件的組件。可用來檢舉某些病毒程序所編譯的程序來自哪臺機(jī)器。
四、 數(shù)據(jù)目錄結(jié)構(gòu)(Data Directory)。
DataDirectory是OptionalHeader的最后128個(gè)字節(jié),也是IMAGE_NT_HEADERS的最后一部分?jǐn)?shù)據(jù)。它由16個(gè)IMAGE_DATA_DIRECTORY結(jié)構(gòu)組成的數(shù)組構(gòu)成。IMAGE_DATA_DIRECTORY的結(jié)構(gòu)如下:
每一個(gè)IMAGE_DATA_DIRECTORY都是對應(yīng)一個(gè)PE文件重要的數(shù)據(jù)結(jié)構(gòu)。他們分別如下:
VirtualAddress指的是對應(yīng)數(shù)據(jù)結(jié)構(gòu)的RVA地址;iSize指的是對應(yīng)數(shù)據(jù)結(jié)構(gòu)的大小(字節(jié)單位)。一個(gè)PE文件一般只包含其中的一部分,也就是其中一部分?jǐn)?shù)據(jù)結(jié)構(gòu)是有數(shù)據(jù)的;另一部分則都是0。比如,EXE文件一般都存在IMAGE_DIRECTORY_ENTRY_IMPORT(輸入表),而不存在IMAGE_DIRECTORY_ENTRY_EXPORT(輸出表)。而DLL則兩者都包含。下圖就是某一個(gè)PE文件的數(shù)據(jù)目錄:
五、 Section表。
Section表緊跟在PE header后面。由IMAGE_SECTION_HEADER數(shù)據(jù)結(jié)構(gòu)組成的數(shù)組。每一個(gè)包含了對應(yīng)Section在PE文件中的屬性和偏移位置。
這里不是所有的成員都是有用的。
l Name1: 塊名,這是一個(gè)8位ASCII碼名,用來定義塊名。多數(shù)塊名以一個(gè)"."開始(如.text),盡管許多PE文檔都認(rèn)為這個(gè)"."實(shí)際上并不是必須的。值得注意的是,如果塊名超過8位,則最后的NULL不存在。帶有一個(gè)"$"的區(qū)塊名字會從鏈接器那里得到特殊的對待,前面帶"$"的相同名字的區(qū)塊被合并,在合并后的區(qū)塊中它們是按"$"后面的字符字母順序進(jìn)行合并的。
l Misc.VirtualSize : 指出實(shí)際的、被使用的區(qū)塊大小。如果VirtualSize大于SizeOfRawData,那么SizeOfRawData來自于可執(zhí)行文件初始化數(shù)據(jù)的大小,與VirtualSize相差的字節(jié)用0填充。這個(gè)字段在OBJ文件中設(shè)為0。
l VirtualAddress : 該塊裝載到內(nèi)存中的RVA。這個(gè)地址是按照內(nèi)存頁對齊的,它的數(shù)值總是SectionAlignment的整數(shù)倍。在MS工具中,第一塊的默認(rèn)RVA為1000H.在OBJ中,該字段沒意義。如果該值為1000H, PE文件被加載到400000H,那么該Section的起始地址為401000H。
l SizeOfRawData : 該塊在磁盤文件中所占的大小。在可執(zhí)行文件中,這個(gè)值必須是PE頭部指定的文件對齊大小的倍數(shù)。如果是0,則說明區(qū)塊中的數(shù)據(jù)是未初始化的。該塊在磁盤文件中所占的大小,這個(gè)數(shù)值等于VirtualSize字段的值按照FileAlignment的值對齊以后的大小。例如,FileAlignment的大小為1000H,如果VirtualSize中的塊長度為2911,則SizeOfRawData為3000H}
l PointerToRawData : 該塊在磁盤文件中的偏移。對于可執(zhí)行文件,這個(gè)值必須是PE頭部指定的文件對齊大小的倍數(shù)。
l PointerToRelocations : 這部分在EXE文件中無意義。在OBJ文件中,表示本塊重定位信息的偏移量。在OBJ文件中如果不是零,則會指向一個(gè)IMAGE_RELOCATION的數(shù)據(jù)結(jié)構(gòu)。
l NumberOfRelocations : 由PointerToRelocations指向的重定位的數(shù)目。
l NumberOfLinenumbers : 由NumberOfRelocations指向的行號的數(shù)目,只在COFF樣式的行號被指定時(shí)使用。
l Characteristics : 塊屬性,該字段是一組指出塊屬性(如代碼/數(shù)據(jù)/可讀/可寫等)的標(biāo)志。多個(gè)標(biāo)志值通過OR操作形成Characteristics的值。這些標(biāo)志很多都可以通過鏈接器/SECTION選項(xiàng)設(shè)置。
位
數(shù)據(jù)位在Windows.inc中的預(yù)定義
為1時(shí)的含義
5
IMAGE_SCN_CNT_CODE (00000020H)
節(jié)中包含代碼
6
IMAGE_SCN_CNT_INITIALIZED_DATA (00000040H)
節(jié)中包含已初始化數(shù)據(jù)
7
IMAGE_SCN_CNT_UNINITIALIZED_DATA (00000080H)
節(jié)中包含未初始化數(shù)據(jù)
25
IMAGE_SCN_MEM_DISCARDABLE (02000000H)
節(jié)中的數(shù)據(jù)在進(jìn)程開始后將被丟棄
26
IMAGE_SCN_MEM_NOT_CACHED (04000000H)
節(jié)中的數(shù)據(jù)不會經(jīng)過緩存
27
IMAGE_SCN_MEM_NOT_PAGED (08000000H)
節(jié)中的數(shù)據(jù)不會被交換到磁盤
28
IMAGE_SCN_MEM_SHARED (10000000H)
節(jié)中的數(shù)據(jù)將被不同的進(jìn)程所共享
29
IMAGE_SCN_MEM_EXECUTE (20000000H)
映射到內(nèi)存后的頁面包含可執(zhí)行屬性
30
IMAGE_SCN_MEM_READ (40000000H)
映射到內(nèi)存后的頁面包含可讀屬性
31
IMAGE_SCN_MEM_WRITE (80000000H)
映射到內(nèi)存后的頁面包含可寫屬性
六、 PE文件各個(gè)Section。
PE文件的Sections部分包含了文件的內(nèi)容。包括代碼,數(shù)據(jù),資源和其他可執(zhí)行信息。每一個(gè)Section由一個(gè)頭部和一個(gè)數(shù)據(jù)部分組成。所有的頭部都存放在緊跟PE header后的Section表內(nèi)。
1. 執(zhí)行代碼。
在NT Windows系統(tǒng)內(nèi),所有的PE文件的代碼段都存放在一個(gè)Section內(nèi),通常命名為.text(MS)或CODE(Borland)。這一段包含了早先提起的AddressOfEntryPoint多指地址的指令及輸入表中的jump thunk table。
2. 數(shù)據(jù)。
l .bss段存放未初始化的數(shù)據(jù),包括函數(shù)內(nèi)或源模塊內(nèi)聲明的靜態(tài)變量。
l .rdata段存放只讀數(shù)據(jù),比如常字符串,常量,調(diào)試指示信息。
l .data 段存放其他所有的數(shù)據(jù)(除了自動化變量,其存放在棧中)。比如程序的全局變量。
3. 資源。
.rsrc段包含了一個(gè)模塊的資源信息。以資源樹的結(jié)構(gòu)存放數(shù)據(jù)。需要用工具來查看。
4. 輸出數(shù)據(jù)。
.edata段包含了PE文件的輸出目錄(Export Directory)。
5. 輸入數(shù)據(jù)。
.idata包含了PE文件的輸入目錄和輸入地址表。
6. 調(diào)試信息。
調(diào)試信息存放在.debug段。PE文件也支持單獨(dú)的調(diào)試文件。Debug段包含調(diào)試信息,但是調(diào)試目錄卻存放在.rdata內(nèi)。
7. 線程局部存儲。(TLS)
Windows支持每一個(gè)進(jìn)程包含多個(gè)線程。每一個(gè)線程有其私有的存儲空間(TLS)去存放線程自身的數(shù)據(jù)。鏈接器都會為進(jìn)程創(chuàng)建一個(gè).tls段來存放TLS模板。當(dāng)進(jìn)程創(chuàng)建一個(gè)線程時(shí),系統(tǒng)就會按照這個(gè)模板創(chuàng)建一個(gè)線程私有的局部存儲空間。
8. 基重定位。
當(dāng)加載器加載PE文件到內(nèi)存的時(shí)候,有時(shí)候不一定是其預(yù)期的基地址。那么就需要調(diào)整內(nèi)部指令的相對地址。所有需要調(diào)整的地址都存放在.reloc段內(nèi)。
七、輸出Section.
?
這個(gè)Section跟DLL關(guān)系比較密切。DLL一般定義兩種函數(shù),內(nèi)部使用的,和輸出到外部給其他調(diào)用程序使用的。輸出到外部的函數(shù)就存儲在這個(gè)Section內(nèi)。
DLL輸出函數(shù)分兩種方式,通過名稱和通過序號輸出。當(dāng)其他程序需要調(diào)用DLL的時(shí)候,調(diào)用GetProcAddress,通過設(shè)置需要調(diào)用的函數(shù)名稱或函數(shù)序號可以調(diào)用DLL內(nèi)部輸出的函數(shù)。
那么GetProcAddress是怎么獲取DLL中真正的輸出函數(shù)地址呢?以下是詳細(xì)的解說。
PE頭的數(shù)據(jù)目錄(DATA DIRECTORY)數(shù)組的第一個(gè)成員對應(yīng)的(通過其中的RVA地址可獲得)數(shù)據(jù)結(jié)構(gòu)是IMAGE_EXPORT_DIRECTORY(這里稱為輸出目錄)。
成員
大小
描述
Characteristics
DWORD
未定義,總是0
TimeDateStamp
DWORD
輸出表的創(chuàng)建時(shí)間。與IMAGE_NT_HEADER.FileHeader.TimeDateStamp有相同的定義
MajorVersion
WORD
輸出表的主版本號。未使用,為0
MinorVersion
DWORD
輸出表的次版本號。未使用,為0
nName
DWORD
指向一個(gè)ASCII字符串的RVA,這個(gè)字符串是與這些輸出函數(shù)關(guān)聯(lián)的DLL的名稱(比如,Kernel32.dll)。這個(gè)值必須定義,因?yàn)槿绻鸇LL文件的名稱如果被修改,加載器將使用這里的名稱。
nBase
DWORD
這個(gè)字段包含用于這個(gè)可執(zhí)行文件輸出表的起始序數(shù)值(基數(shù))。正常情況下為1,但不是一定是。當(dāng)通過序數(shù)來查詢一個(gè)輸出函數(shù)時(shí),這個(gè)值會被從序數(shù)里減去。(比如,如果nBase = 1,被查詢的函數(shù)的序數(shù)是3,那么這個(gè)函數(shù)在序號表的索引是3 -1 = 2)。
NumberOfFunctions
DWORD
輸出地址表(EAT)的條目數(shù)。其中一些條目可能是0,意味著這個(gè)序數(shù)值沒有代碼和數(shù)據(jù)輸出。
NumberOfNames
DWORD
輸出名稱表(ENT)的條目數(shù)。這個(gè)值總是大于或等于NumberOfFunctions。小于的情況發(fā)生在符號只通過序數(shù)來輸出時(shí)。另外,當(dāng)被賦值的序數(shù)里有數(shù)字間隔時(shí)也會有小于的情況。這個(gè)值也是輸出序數(shù)表的長度。
AddressOfFunctions
DWORD
輸出地址表(EAT)的RVA。輸出地址表本身是一個(gè)RVA數(shù)組,數(shù)組中的每一個(gè)非零的RVA都對應(yīng)一個(gè)被輸出的符號。
AddressOfNames
DWORD
輸出名稱表(ENT)的RVA。輸出名稱表本身是一個(gè)RVA數(shù)組。數(shù)組中的每一個(gè)非零的RVA都向一個(gè)ASCII字符串。每一個(gè)字符串都對應(yīng)一個(gè)通過名稱輸出的符號。這個(gè)表是排序。這允許加栽器在查詢一個(gè)被輸出的符號時(shí)可用二進(jìn)制查找方式。名稱的排序是二進(jìn)制的,而不是按字母。
AddressOfNameOrdinals
DWORD
輸出序數(shù)表(EOT)的RVA。這個(gè)表將ENT中的數(shù)組索引映射到相應(yīng)的輸出地址條目。
實(shí)際上,IMAGE_EXPORT_DIRECTORY結(jié)構(gòu)指向三個(gè)數(shù)組和一個(gè)ASCII字符串表。其中重要的是輸出地址表(EAT,即AddressOfFunctions指向的表), 輸出函數(shù)地址指針(RVA)構(gòu)成了這個(gè)表。而ENT和EOT則是可以一起合作來獲取EAT里對應(yīng)的地址數(shù)據(jù)。下圖演示了這個(gè)過程。
這個(gè)被加載的DLL的名稱是F00.DLL??偣草敵隽怂膫€(gè)函數(shù),其RVA地址分別為0x400042、0x400156、0x401256和0x400520。一個(gè)外部調(diào)用程序需要調(diào)用其中一個(gè)名為”Bar”的函數(shù),那么它先在輸出名稱表(ENT)里查找名稱為Bar的函數(shù),找到后,根據(jù)其在輸出序號表(EOT)中對應(yīng)的索引號,獲取其中的數(shù)值為EAT中的索引值,這里是4,然后從EAT中根據(jù)索引4獲取其真正的RVA地址0x400520。以下是幾個(gè)注意點(diǎn):
l 輸出序號表(EOT)的存在就是為了是EAT跟ENT之間產(chǎn)生關(guān)聯(lián)。每一個(gè)ENT內(nèi)的成員(函數(shù)名)有且只有一個(gè)EAT內(nèi)的成員(函數(shù)地址)對應(yīng)。但是一個(gè)EAT內(nèi)的成員并不是只有一個(gè)ENT內(nèi)的成員對應(yīng)。比如,有的函數(shù)存在別名的話,就會出現(xiàn)多個(gè)ENT內(nèi)的成員都對應(yīng)一個(gè)EAT內(nèi)的成員。
l 如果已經(jīng)獲得一個(gè)函數(shù)的序號值,那么就可以直接到EAT內(nèi)獲得其RVA地址,而不需要經(jīng)過ENT和EOT進(jìn)行查找。但是這樣的按序號輸出的DLL不易于維護(hù)。
l 通常情況下,EAT的個(gè)數(shù)(NumberOfFunctions)必須小于或等于ENT的個(gè)數(shù)(NumberOfNames)。只有在一個(gè)函數(shù)按序號輸出時(shí)(其在ENT和EOT表里沒有對應(yīng)的數(shù)據(jù)),ENT的數(shù)量才有可能少于EAT的數(shù)量。比如,總共有70個(gè)函數(shù)輸出,但是在ENT表里只有40個(gè),這就意味著剩余的30個(gè)函數(shù)是靠序號輸出的。那么我們?nèi)绾沃滥男┦侵苯涌啃蛱栞敵龅哪?#xff1f;只有通過排除法來獲得。把存在在EOT表里的序號從EAT里排除出去,剩下的就是靠序號輸出的函數(shù)。
l 當(dāng)通過一個(gè)序號值來獲取EAT內(nèi)的函數(shù)RVA時(shí),需要把這個(gè)序號值減去nBase的值來獲取在EAT表里真正的索引位置。而通過名稱查找則不需要這么做。
l 輸出轉(zhuǎn)向。某些時(shí)候,你從一個(gè)DLL中調(diào)用的一個(gè)函數(shù)可能位于另一個(gè)DLL中。這就叫輸出轉(zhuǎn)向。比如,Kernel32.dll中的HeapAlloc就是轉(zhuǎn)到調(diào)用NTDLL.dll中的RtlAllocHeap。這種轉(zhuǎn)向是在鏈接的時(shí)候,在.DEF文件中定義一個(gè)特殊的指令來實(shí)現(xiàn)的。那么當(dāng)一個(gè)函數(shù)被轉(zhuǎn)向后,在其所在EAT表里對應(yīng)的數(shù)據(jù)便不是其地址,而是一個(gè)指向表明被轉(zhuǎn)向的DLL和函數(shù)的ASCII字符串的地址指針。
上圖就是Kernel32.dll的輸出函數(shù)表,其中HeapAlloc的RVA值0x00009048就是一個(gè)指向“NTDLL.RtlAllocHeap”的指針。
八 、 輸入Section.
輸入Section通常位于.idata段內(nèi)。它包含了所有程序需要用到的來自其他DLL的函數(shù)的信息。Windows加載器負(fù)責(zé)加載所有程序用到的DLL到進(jìn)程空間。然后為進(jìn)程找到所有其需要用到的函數(shù)的地址。下面描述這個(gè)過程:
PE頭的數(shù)據(jù)目錄(DATA DIRECTORY)數(shù)組的第二個(gè)成員對應(yīng)的(通過其中的RVA地址可獲得)數(shù)據(jù)結(jié)構(gòu)是輸入表。輸入表是一個(gè) IMAGE_IMPORT_DESCRIPTOR數(shù)據(jù)結(jié)構(gòu)的數(shù)組。沒有字段表明這個(gè)數(shù)組的個(gè)數(shù),只是它的最后一個(gè)成員的數(shù)據(jù)都為0。每一個(gè)數(shù)組成員都對應(yīng) 一個(gè)DLL。
成員
大小
描述
OriginalFirstThunk
DWORD
指向輸入名稱表(INT)的RVA。INT是由IMAGE_THUNK_DATA數(shù)據(jù)結(jié)構(gòu)構(gòu)成的數(shù)組。數(shù)組中的每一個(gè)成員定義了一個(gè)輸入函數(shù)的信息,數(shù)組最后以一個(gè)內(nèi)容為0的IMAGE_THUNK_DATA結(jié)束。
TimeDateStamp
DWORD
當(dāng)執(zhí)行文件不與被輸入的DLL進(jìn)行綁定時(shí),這個(gè)字段為0。當(dāng)以舊的方式綁定時(shí),這個(gè)字段包括時(shí)間/日期。當(dāng)以新的樣式綁定時(shí),這個(gè)字段為-1。
ForwarderChain
DWORD
這是第一個(gè)被轉(zhuǎn)向的API的索引。老樣式綁定的定義。
Name
DWORD
指向被輸入DLL的ASCII字符串的RVA。
FirstThunk
DWORD
指向輸入地址表(IAT)的RVA。IAT也是一個(gè)IMAGE_THUNK_DATA數(shù)據(jù)結(jié)構(gòu)的數(shù)組。
由上表可知,輸入表主要是通過IMAGE_THUNK_DATA這個(gè)數(shù)據(jù)結(jié)構(gòu)導(dǎo)入函數(shù)。下面是IMAGE_THUNK_DATA的描述:
這是一個(gè)DWORD聯(lián)合體數(shù)據(jù)結(jié)構(gòu)。其實(shí)這里對輸入表有意義的字段只有兩個(gè),Ordinal和 AddressOfData。當(dāng)這個(gè)DWORD數(shù)據(jù)的最高位為1的時(shí)候,代表函數(shù)以序號的方式導(dǎo)入,Ordinal的低31位就是輸入函數(shù)在其DLL內(nèi)的 導(dǎo)出序號。當(dāng)這個(gè)DWORD的數(shù)據(jù)最高位為0的時(shí)候,代表函數(shù)以字符串方式導(dǎo)入。AddressOfData就是一個(gè)指向用來導(dǎo)入函數(shù)名稱的 IMAGE_IMPORT_BY_NAME的數(shù)據(jù)結(jié)構(gòu)的RVA。(這里用來判斷最高位的值0x8000000,預(yù)定義值為 IMAGE_ORDINAL_FLAG32。)
l Hint字段也表示函數(shù)的序號,主要是用來便與加載器快速查找在導(dǎo)入的DLL的函數(shù)導(dǎo)出表,當(dāng)通過這個(gè)序號查找到的函數(shù)跟所要導(dǎo)入的函數(shù)不匹配時(shí),就改為通過名稱查找。不過這個(gè)字段是可選的,有些編譯器把它設(shè)置為0。
l Name1字段定義了導(dǎo)入函數(shù)的名稱字符串,這是一個(gè)以0為結(jié)尾的字符串。
整個(gè)過程有點(diǎn)復(fù)雜,下圖給出一個(gè)相對清晰的描述。
1. 加載器首先讀入IMAGE_IMPORT_DESCRIPTOR,獲得需要加載的動態(tài)庫User32.DLL。
2. 加載 器根據(jù)OriginalFirstThunk或FirstThunk所指向的IMAGE_THUNK_DATA數(shù)組的RVA來獲取真正的輸入函數(shù)名稱表 (INT)和輸入函數(shù)地址表(IAT)。這里這兩個(gè)表所指向的是同一個(gè)IMAGE_IMPORT_BY_NAME數(shù)據(jù)結(jié)構(gòu)的RVA。
3. 加載器根據(jù)IMAGE_IMPORT_BY_NAME的序號或名稱到導(dǎo)入的DLL(user32.dll)函數(shù)導(dǎo)出表中獲取導(dǎo)入函數(shù)的地址。然后把這個(gè)地址替換掉FirstThunk所指向的函數(shù)輸入地址表中的數(shù)據(jù)。
上圖已經(jīng)說明了為什么會存在兩個(gè)一模一樣的IMAGE_THUNK_DATA數(shù)組。答案就是在這個(gè)PE文件被裝 入內(nèi)存后,FirstThunk所指向的IMAGE_THUNK_DATA內(nèi)的值將被改為用來存儲導(dǎo)入函數(shù)的真正的地址。我們稱之為IAT(Import Address Table). 其實(shí)在數(shù)據(jù)目錄表DATA_DIRECTORY中的第13項(xiàng)(索引為12)直接給出了這個(gè)IAT的地址和大小. 可以直接通過數(shù)據(jù)目錄快速獲得這個(gè)IAT表. 但是這樣還不足于說明為什么會存在兩個(gè)一樣的IMAGE_THUNK_DATA數(shù)組。INT好象沒有存在的 必要。這里要涉及到一個(gè)綁定的概念。
綁定:
l 在 加載器加載PE文件的時(shí)候,先需要檢查輸入表獲取要輸入的DLL的名稱,然后把DLL映射到進(jìn)程的地址空間。再檢查IAT表里的 IMAGE_THUNK_DATA數(shù)組所指向的字符串獲取要輸入函數(shù)的名稱,然后用輸入函數(shù)的地址替換掉IMAGE_THUNK_DATA數(shù)組內(nèi)的數(shù)據(jù)。 整個(gè)過程需要相對比較長的時(shí)間。如果事先在鏈接的時(shí)候就把這些地址寫入IAT中,那么就會節(jié)省很多時(shí)間。這就是綁定的由來。
l 再綁定后,PE文件IAT表里放著是導(dǎo)入DLL輸出函數(shù)的實(shí)際內(nèi)存地址。要使綁定的結(jié)果能正常運(yùn)行,需要兩個(gè)條件:
n 在加載PE文件所需的DLL的時(shí)候,DLL應(yīng)該被映射到它們自己PE頭里定義好的ImageBase這個(gè)地址。
n 被執(zhí)行綁定后,PE文件所導(dǎo)入DLL的函數(shù)導(dǎo)出的函數(shù)表里的函數(shù)符號的位置不能發(fā)生改變。
l 這 兩個(gè)條件當(dāng)然很難在長時(shí)間內(nèi)很難滿足。比如,這個(gè)被導(dǎo)入的DLL發(fā)生了變化,增加了新的函數(shù)輸出。那么其原來輸出表內(nèi)的函數(shù)符號的位置發(fā)生了變化。那么這 個(gè)時(shí)候,原先綁定的結(jié)果就會發(fā)生錯誤。為了解決這個(gè)問題,所以就同時(shí)定義了INT這個(gè)表。讓它做為IAT的備份。一旦預(yù)先綁定好的IAT發(fā)生了錯誤,那么 加載器便會從INT里獲取所需要的信息。
這就是為什么會存在兩個(gè)一模一樣的IMAGE_THUNK_DATA數(shù)組真正的緣由。微軟的鏈接器一般總會在生成IAT的同時(shí)生成一個(gè)INT;而Borland的鏈接器卻只生成IAT。所以Borland生成的PE文件是不能被綁定的。
那么,當(dāng)加載器加載PE文件的時(shí)候,需要判斷當(dāng)前的綁定是否有效。在數(shù)據(jù)目錄(Data Directory)的第12項(xiàng)(序號為11)所指向的一組數(shù)據(jù)結(jié)構(gòu)IMAGE_BOUND_IMPORT_DESCRIPTOR就是用來檢查這個(gè)有效性的。
成員
大小
描述
TimeDateStamp
DWORD
必須與被輸入的DLL的PE頭內(nèi)的TimeDateStamp一樣,如果不一致,那么加載器就會認(rèn)為綁定的對象有誤,需要重新修補(bǔ)輸入表。
OffsetModuleName
WORD
第一個(gè)IMAGE_BOUND_IMPORT_DESCRIPTOR結(jié)構(gòu)到被輸入DLL名稱的偏移(非RVA)。
NumberOfModuleForwarderRefs
WORD
包含緊跟在這個(gè)結(jié)構(gòu)后面IMAGE_BOUND_FORWARDER_REF的數(shù)目。
這個(gè)結(jié)構(gòu)跟IMAGE_BOUND_IMPORT_DESCRIPTOR其實(shí)很象除了最后一個(gè)成員。它主要用于,在被導(dǎo)入的DLL中的某一個(gè)函數(shù)是轉(zhuǎn)向?qū)С鰰r(shí),這個(gè)結(jié)構(gòu)就用來給出所轉(zhuǎn)向到的函數(shù)的信息。
延遲加載:
除了通過加載器建立IAT表以外,程序調(diào)用外部DLL函數(shù)還有另外一種方式。就是先通過LoadLibrary動態(tài)加載DLL,然后用GetProcAddress獲取所需函數(shù)的地址。這種方式稱之為“延遲加載”。
數(shù)據(jù)目錄(Data Directory)第14個(gè)成員(序號是13)IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT條目就是指向延遲加載的數(shù)據(jù)。這個(gè)數(shù)據(jù)就是由一個(gè)名叫ImgDelayDescr數(shù)據(jù)結(jié)構(gòu)組成的數(shù)組。
ImgDelayDescr = packed record
grAttrs: DWORD;
szName: DWORD;
phmod: PDWORD;
pIAT: TImageThunkData32;
pINT: TImageThunkData32;
pBoundIAT: TImageThunkData32;
pUnloadIAT: TImageThunkData32;
dwTimeStamp: DWORD;
end;
成員
描述
grAttrs
設(shè)為1的時(shí)候,下面的各個(gè)成員都是RVA,否則是VA(虛擬地址)。
szName
指向一個(gè)DLL名稱的RVA。
phmod
指向一個(gè)HMODULE的RVA。
pIAT
指向DLL的IAT的RVA。
pINT
指向DLL的INT的RVA。
pBoundIAT
可選的綁定IAT的RVA。
pUnloadIAT
指向DLL的IAT的未綁定拷貝
dwTimeStamp
延遲裝載的輸入DLL的時(shí)間/日期。通常是0。
九、 Windows加載器
加載器讀取一個(gè)PE文件的過程如下:
1. 先讀入PE文件的DOS頭,PE頭和Section頭。
2. 然后根據(jù)PE頭里的ImageBase所定義的加載地址是否可用,如果已被其他模塊占用,則重新分配一塊空間。
3. 根據(jù)Section頭部的信息,把文件的各個(gè)Section映射到分配的空間,并根據(jù)各個(gè)Section定義的數(shù)據(jù)來修改所映射的頁的屬性。
4. 如果文件被加載的地址不是ImageBase定義的地址,則重新修正ImageBase。
5. 根據(jù)PE文件的輸入表加載所需要的DLL到進(jìn)程空間。
6. 然后替換IAT表內(nèi)的數(shù)據(jù)為實(shí)際調(diào)用函數(shù)的地址。
7. 根據(jù)PE頭內(nèi)的數(shù)據(jù)生成初始化的堆和棧。
8. 創(chuàng)建初始化線程,開始運(yùn)行進(jìn)程。
這里要提的是加載PE文件所需DLL的過程是建立在六個(gè)底層的API上。
LdrpCheckForLoadedDll:檢查要加載的模塊是否已經(jīng)存在。
LdrpMapDll:映射模塊和所需信息到內(nèi)存。
LdrpWalkImportDescriptor:遍歷模塊的輸入表來加載其所需的其他模塊。
LdrpUpdateLoadCount:計(jì)數(shù)模塊的使用次數(shù)。
LdrpRunInitializeRoutines:初始化模塊。
LdrpClearLoadInProgress:清楚某些標(biāo)志,表明加載已經(jīng)完成。
十、 插入代碼到PE文件
有三種方式可以插入代碼到PE文件:
1. 把代碼加入到一個(gè)存在的Section的未用空間里。
2. 擴(kuò)大一個(gè)存在的Section,然后把代碼加入。
3. 新增一個(gè)Section。
方法一、增加代碼到一個(gè)存在的Section。
首先我們需要找到一個(gè)被映射到一個(gè)塊有執(zhí)行權(quán)限的Section。最簡單的方式就是直接利用CODE Section。
然后我們需要查找這塊Section內(nèi)的多余空間(也就是填滿了00h)。我們知道一個(gè)Section有兩個(gè)數(shù)據(jù)來表示其大小。 VirtualSize和SizeOfRawData。這個(gè)VirtualSize代表Section里代碼實(shí)際所占用的磁盤空間。 SizeOfRawData代表根據(jù)磁盤對齊后所占的空間。通常SizeofRawData都會比VirtualSize要大。如下圖。
圖中的SizeOfRawData是0002A000,而VirtualSize是00029E88。當(dāng)PE文件被加載到內(nèi)存的時(shí)候,他們之間 的多余空間的數(shù)據(jù)是不會被加載到內(nèi)存去。那么如果要把加入到這個(gè)間隙中間的代碼也被加載到內(nèi)存去,就需要修改VirtualSize的值,這里把 VirtualSize的值可以改為00029FFF。這樣,我們就有了一小段空間加入自己的代碼。下面需要做的就是先找到PE文件的入口點(diǎn) OriginalEntryPoint,比如這個(gè)OriginalEntryPoint是0002ADB4,ImageBase是400000,那么入口 點(diǎn)的實(shí)際虛擬地址是0042ADB4。然后計(jì)算出自己代碼的起始RVA,更換掉PE頭內(nèi)的OriginalEntryPoint,在自己的代碼最后加上:
MOV EAX,00042ADB4
JMP EAX
這樣就可以在PE文件被加載的時(shí)候,先運(yùn)行自己的代碼,然后再運(yùn)行PE文件本身的代碼。成功的把代碼加入到了PE文件內(nèi)。
方法二、擴(kuò)大一個(gè)存在的Section來加入代碼。
如果在一個(gè)Section末尾沒有足夠的空間存放自己的代碼,那么另外一種方法就是擴(kuò)大一個(gè)存在的Section。一般我們只擴(kuò)大PE文件最尾部的Section,因?yàn)檫@樣可以避免很多問題,比如對其他Section的影響。
首先我們的找到最后一個(gè)Section使之可讀可執(zhí)行。這可以通過修改其對應(yīng)Section頭部的Characteristics來獲得。然后 根據(jù)PE頭內(nèi)文件對齊的大小,修改其SizeOfRawData。比如文件對齊的大小是200h,原先SizeOfRawData=00008000h, 那么我們增加的空間大小應(yīng)該是200h的整數(shù)倍,修改完的SizeOfRawData至少是00008200h。增加完空間后,需要修改PE頭內(nèi)的兩個(gè)字 段的數(shù)值,SizeOfCode和SizeOfInitialishedData。分別為它們增加200h的大小。這樣我們就成功的擴(kuò)大了一個(gè) Section,然后根據(jù)方法一內(nèi)的方式把代碼加入到增加的空間。
方法三、新增一個(gè)Section來加入代碼。
如果要加入的代碼很多,那么就需要新增一個(gè)Section來存放自己的代碼。
l 首先,我們需要在PE頭內(nèi)找到NumberOfSections,使之加1。
l 然后,在文件末尾增加一個(gè)新的空間,假設(shè)為200h,記住起始行到PE文件首部的偏移。假如這個(gè)值是00034500h。同時(shí)將PE頭內(nèi)的SizeOfImage的值加200h。
l 然后,找到PE頭內(nèi)的Section頭部。通常在Section頭部結(jié)束到Section數(shù)據(jù)部分開始間會有一些空間,找到Section頭部的最后然后加入一個(gè)新的頭部。假設(shè)最后一個(gè)Section頭部的數(shù)據(jù)是:
1. Virtual offset : 34000h
2. Virtual size : 8E00h
3. Raw offset: 2F400h
4. Raw size : 8E00h
而文件對齊和Section對齊的數(shù)據(jù)分別是:
5. Section Alignment : 1000h
6. File Alignment : 200h
l 那么新增加的Section必須與最后一個(gè)Section的邊界對齊。它的數(shù)據(jù)分別:
1. Virtual offset : 3D000h (因?yàn)樽詈笠粋€(gè)Section的最后邊界是34000h + 8E00h = 3CE00h,加上Section對齊,則Virtual offset的值為3D000h)。
2. Virtual size : 200h。
3. Raw offset: 00034500h。
4. Raw size: 200h.
5. Characteristics : E0000060 (可讀、可寫、可執(zhí)行)。
l 最后,只需要修改一下PE頭內(nèi)的SizeOfCode和SizeOfInitialishedData兩個(gè)字段,分別加上200h。
l 剩下的就是按照方法一的方式把代碼放入即可。
十一、 增加執(zhí)行文件的輸入表項(xiàng)目。
在一些特殊用途上,我們需要為執(zhí)行文件或DLL增加其不包含的API。那么可以通過增加這些API在輸入表中的注冊來達(dá)到。
1. 每一個(gè)輸入的DLL都有一個(gè)IMAGE_IMPORT_DESCRIPTOR (IID)與之對應(yīng)。PE頭中的最后一個(gè)IID是以全0來表示整個(gè)IID數(shù)組的結(jié)束。
2. 每一個(gè)IID至少需要兩個(gè)字段Name1和FirstThunk。其他字段都可以設(shè)置為0。
3. 每一個(gè)FirstThunk的數(shù)據(jù)必須是一個(gè)指向IMAGE_THUNK_DATA數(shù)組的RVA。每一個(gè)IMAGE_THUNK_DATA又包含了指向一個(gè)API名稱的RVA。
4. 如果IID數(shù)組發(fā)生改變,那么只需要修改數(shù)據(jù)目錄數(shù)組中對應(yīng)輸入表的數(shù)據(jù)結(jié)構(gòu)IMAGE_DATA_DIRECTORY的iSize。
增加一個(gè)新的IID到輸入表的末尾,就是把輸入表末尾的全是0的IID修改成增加的新的IID,然后在增加一個(gè)全0的IID作為輸入表新的末 尾。但是如果在輸入表末尾沒有空間的話,那就需要拷貝整個(gè)輸入表到一個(gè)新的足夠的空間,同時(shí)修改數(shù)據(jù)目錄數(shù)組對應(yīng)輸入表的數(shù)據(jù)結(jié)構(gòu) IMAGE_DATA_DIRECTORY的RVA和iSize。
步驟一、增加一個(gè)新的IID。
-
把整個(gè)IID數(shù)組移到一個(gè)有足夠空間來增加一個(gè)新的IID的地方。這個(gè)地方可以是.idata段的末尾或是新增一個(gè)Section來存放。
-
修改數(shù)據(jù)目錄數(shù)組對應(yīng)輸入表的數(shù)據(jù)結(jié)構(gòu)IMAGE_DATA_DIRECTORY的RVA和iSize。
-
如果必要,將存放新IID數(shù)組的Section大小按照Section Alignment向上取整(比如,原來大小是1500h, 而section Alignment為1000h,則調(diào)整為2000h)以便于整個(gè)段可以被映射到內(nèi)存。
-
運(yùn)行移動過IID數(shù)組的執(zhí)行文件,如果正常的話,則進(jìn)行第二步驟。如果不工作的話,需要檢查新增的IID是否已經(jīng)被映射到內(nèi)存及IID數(shù)組新的偏移位置是否正確。
步驟二、增加一個(gè)新的DLL及其需要的函數(shù)。
-
在.idata節(jié)內(nèi)增加兩個(gè)以null結(jié)尾的字符串,一個(gè)用來存放新增的DLL的名字。 一個(gè)用來存放需要導(dǎo)入的API的名稱。這個(gè)字符串前需要增加一個(gè)為null的WORD字段來構(gòu)成一個(gè) Image_Import_By_Name數(shù)據(jù)結(jié)構(gòu)。
-
計(jì)算這個(gè)新增的DLL名稱字符串的RVA.
-
把這個(gè)RVA賦予新增的IID的Name1字段。
-
再找到一個(gè)DWORD的空間,來存放Image_Import_by_name的RVA。這個(gè)RVA就是新增DLL的IAT表。
-
計(jì)算上面DWORD空間的RVA,將其賦予新增IID的FirstThunk字段。
-
運(yùn)行修改完的程序。
-
====================
-
PE文件基礎(chǔ)補(bǔ)注
關(guān)鍵詞:?PE文件??地址轉(zhuǎn)換?IAT??IMAGE_IMPORT_BY_NAME
前言:?最近學(xué)習(xí)PE,?略有心得,?拿來和大家分享.?
感謝:?小蝦斑斑,?非安全??,Bookworm對我的幫助.附件:pe.rar?
1.IMAGE_SECTION_HEADER小結(jié):
??
1.1???獲得節(jié)表數(shù)?:NumberOfSections?=?NtHeader->FileHeader.NumberOfSections;
1.2???節(jié)表獲得方法
??????
??????方法1.因?yàn)镹T頭之后就是節(jié)表,故,節(jié)表頭地址就是nt頭地址加上NT結(jié)構(gòu)大小.
??????SectionHeader=(PIMAGE_SECTION_HEADER)((UINT32)NtHeader+(UINT32)(sizeof(IMAGE_NT_HEADERS)));
??
??????方法2.或者用ImageBase+SizeOfHeaders的辦法直接定位.
??????SectionHeader=(PIMAGE_SECTION_HEADER)((UINT32)(NtHeader->OptionalHeader.ImageBase)+
??????????????????????????????????????????(UINT32)(NtHeader->OptionalHeader.SizeOfHeaders));
??
??????方法3.既然節(jié)都是連在一起的,那么,也就可以這樣:?
??????????????SectionHeader=?(PIMAGE_SECTION_HEADER)?(NtHeader?+?1),
??
??????方法4.論壇里面?hmimys?告訴的辦法:????
??????????????SectionHeader=(PIMAGE_SECTION_HEADER)((UINT32)NtHeader+0x18+
???????????????????????????????????????????????????(UINT32)(NtHeader->FileHeader.SizeOfOptionalHeader));
??????????????到現(xiàn)在我還沒有弄懂為什么?hmimys?說最好要用方法4而不用方法3.
????
????
2.?IMAGE_IMPORT_DECSRITOR?小結(jié):
2.1:獲得引入表結(jié)構(gòu)起始地址:
?????
?????方法1:ImportDec?=?(PIMAGE_IMPORT_DESCRIPTOR)(NtHeader->OptionalHeader.DataDirectory[12].VirtualAddress);
????????????這個(gè)方法我覺得理論上是對的,但是我在運(yùn)行的時(shí)候總是得不到正確的地址.后來知道,似乎不能用'12',而要用IMAGE_DIRECTORY_ENTRY_IAT這個(gè)宏
?????方法2:ImportDes?=?(PIMAGE_IMPORT_DESCRIPTOR)((DWORD)(NtHeader->OptionalHeader.DataDirectory)+
????????????????????????????????????????????????????(DWORD)(sizeof(IMAGE_DATA_DIRECTORY)*12));
???
?????方法3?:?ImportDes?=?(PIMAGE_IMPORT_DESCRIPTOR)(NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress-
????????????????Offset?+?(PBYTE)pMapping);
?????注?:?前兩種方法都是從?IAT?中得出?IMAGE_IMPORT_DESCRIPTOR,而后面的那個(gè)是?非安全?大哥教的.?這里有個(gè)疑問:?
??????????3種方法都可以得到?IMAGE_IMPORT_DESCRIPTOR?結(jié)構(gòu),都可以得到函數(shù)名,?區(qū)別在于前兩種方法枚舉的函數(shù)名不全.?
??????????難道說兩個(gè)結(jié)構(gòu)都指向同一個(gè)結(jié)構(gòu)PIMAGE_IMPORT_DESCRIPTOR?
?
2.2??IMAGE_IMPORT_DESCRIPTOR?結(jié)構(gòu)既不是在Import?Symbols中,也不是在IAT?(IMAGE_IMPORT_ADDRESS_TABLE)中。它就是一個(gè)結(jié)構(gòu).?
?????我原來說:"IMAGE_IMPORT_DESCRIPTOR?結(jié)構(gòu)不是在Import?Symbols中,是在IAT?(IMAGE_IMPORT_ADDRESS_TABLE)中。"?有問題.
?????就是因?yàn)檫@個(gè)錯誤的理解,?讓我走了好多死路.
?????
?????這個(gè)是Winnt.h中關(guān)于?IMAGE_SYNMBOL的結(jié)構(gòu)信息
?????typedef?struct?_IMAGE_SYMBOL?{
??????union?{
????????BYTE????ShortName[8];
????????struct?{
????????????DWORD???Short;?????//?if?0,?use?LongName
????????????DWORD???Long;??????//?offset?into?string?table
????????}?Name;
????????PBYTE???LongName[2];
?????}?N;
?????DWORD???Value;
?????SHORT???SectionNumber;
?????WORD????Type;
?????BYTE????StorageClass;
?????BYTE????NumberOfAuxSymbols;
?????}?IMAGE_SYMBOL;
?????
?????typedef?IMAGE_SYMBOL?UNALIGNED?*PIMAGE_SYMBOL;
?????而下面的是IAT:?
?????typedef?struct?_IMAGE_IMPORT_BY_NAME?{
??????WORD????Hint;
??????BYTE????Name[1];
?????}?IMAGE_IMPORT_BY_NAME,?*PIMAGE_IMPORT_BY_NAME;
?????typedef?struct?_IMAGE_IMPORT_DESCRIPTOR?{
??????union?{
??????????DWORD???Characteristics;????????????//?0?for?terminating?null?import?descriptor
??????????DWORD???OriginalFirstThunk;?????????//?RVA?to?original?unbound?IAT?(PIMAGE_THUNK_DATA)
??????};
??????DWORD???TimeDateStamp;??????????????????//?0?if?not?bound,
????????????????????????????????????????????//?-1?if?bound,?and?real?date\time?stamp
????????????????????????????????????????????//?????in?IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT?(new?BIND)
????????????????????????????????????????????//?O.W.?date/time?stamp?of?DLL?bound?to?(Old?BIND)
??????DWORD???ForwarderChain;?????????????????//?-1?if?no?forwarders
??????DWORD???Name;
??????DWORD???FirstThunk;?????????????????????//?RVA?to?IAT?(if?bound?this?IAT?has?actual?addresses)
?????}?IMAGE_IMPORT_DESCRIPTOR;
?????typedef?IMAGE_IMPORT_DESCRIPTOR?UNALIGNED?*PIMAGE_IMPORT_DESCRIPTOR;
?????_IMAGE_IMPORT_DESCRIPTOR?結(jié)構(gòu)聯(lián)合中的OriginalFirstThunk?,?就是到IMAGE_THUNK_DATA的RVA.?
?????如果像下面這樣寫,也許更明白
?????typedef?struct?_IMAGE_THUNK_DATA?{
????????union?{
????????????PBYTE?ForwarderString;
????????????PDWORD?Function;
????????????DWORD?Ordinal;
????????????PIMAGE_IMPORT_BY_NAME?AddressOfData;
????????}?;
?????}?IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;
?????typedef?struct?_IMAGE_IMPORT_DESCRIPTOR?{
????????union?{
????????????DWORD?Characteristics;
????????????PIMAGE_THUNK_DATA?OriginalFirstThunk;
????????}?;
????????DWORD?TimeDateStamp;
????????DWORD?ForwarderChain;
????????DWORD?Name;
????????PIMAGE_THUNK_DATA?FirstThunk;
?????}?IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
?????
3.?地址轉(zhuǎn)換小結(jié)(RVAToOffset):?
???為什么要地址轉(zhuǎn)換,?前人的文章說了很多,下面給出我的轉(zhuǎn)換方法:??
???
???3.1?函數(shù),它能給出RVA返回此RVA所在的節(jié),來自?Matt?Pietrek的書:?
???PIMAGE_SECTION_HEADER?GetEnclosingSectionHeader(DWORD?rva){
????????unsigned?i;
??????PIMAGE_SECTION_HEADER?section?=?IMAGE_FIRST_SECTION32(NtHeader);
??????for?(?i=0;?i?<?NtHeader->FileHeader.NumberOfSections;?i++,section++){
???????????????if?(?(rva?>=section->VirtualAddress)?&&?
?????????????(rva?<?(section->VirtualAddress?+?section->Misc.VirtualSize)))
????????????return?section;
??????}
?????????return?0;?
???}
???注:?hnhuqiong?給的?ollydump300110?的源碼里面也有類似函數(shù),但是,
???????很明顯的有漏洞,那就是若RVA不在任何一個(gè)Section那么函數(shù)會返回最后
???????一個(gè)Section,?而不是像這里返回?0?.下面是原始連接
???http://bbs.pediy.com/showthread.php?threadid=26520
???3.2?RVAToOffset:
???我一直沒有注意的就是'Offset'這個(gè)詞.?Offset其實(shí)還是一個(gè)偏移,只不過是
???在文件中,?要想得到目標(biāo)文件的IAT,?就要將這個(gè)值加上由?MapViewOfFile?返回
???的文件基址指針.
???Offset的的獲得?:?
????????????pSection?=?GetEnclosingSectionHeader(NtHeader->OptionalHeader.DataDirectory???????????????????????????????????????
???????????????????????????????????????????[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress);
????????????Offset?=?(DWORD)?(pSection->VirtualAddress?-?pSection->PointerToRawData);
??
???以獲得IMAGE_THUNK_DATA結(jié)構(gòu)為例,給出用法:?
????????ThunkData?=?(PIMAGE_THUNK_DATA)((DWORD)ImportDes->OriginalFirstThunk?-
???????????????????????????????????????Offset?+?(PBYTE)pMapping);
???呵呵,?(DWORD)ImportDes->OriginalFirstThunk?-Offset?得到的只是文件中的偏移,?
???注意加上由?MapViewOfFile?返回的pMapping.?如果你象我原來一樣,加上的是
???NtHeader->OptionalHeader.ImageBase?,?那么恭喜你,?訪問錯誤.????????????
??
4.?用VC?6.0?+?API獲得IMAGE_IMPORT_BY_NAME結(jié)構(gòu)的一點(diǎn)問題.
???在?VC?里面,??在一個(gè)結(jié)構(gòu)指針比如ThunkData后面加上'->'時(shí),?vc會自動的列出
???結(jié)構(gòu)的成員供你選擇,?十分方便.?但是,?通過ThunkData繼續(xù)想獲得IMAGE_IMPORT_BY_NAME
???結(jié)構(gòu)的時(shí)候,?你在ThunkData后面加'->'時(shí),?出來的是一個(gè)'u1'.?此時(shí)不要疑惑,
???這個(gè)'u1'就是?IMAGE_THUNK_DATA?里面的那個(gè)?union?的名稱,?所以你可以這樣得到
???IMAGE_IMPORT_BY_NAME結(jié)構(gòu):?
???????ImportBN?=?(PIMAGE_IMPORT_BY_NAME)((DWORD)(ThunkData->u1.AddressOfData)-
????????????????????????????????Offset?+(PBYTE)pMapping);
5.???Iczelion的PE教程關(guān)于導(dǎo)入表的描述沒有講清楚,只是說用IMAGE_THUNK_DATA
??????的每個(gè)數(shù)組元素和IMAGE_ORDINAL_FLAG32,比較可以推斷如果某個(gè)函數(shù)是由函數(shù)序數(shù)引出的,
??????我就誤解成用ImportDes->OriginalFirstThunk或者ImportDes->FirstThunk?判斷。是不是錯的很遠(yuǎn)?
??????參考(【翻譯】“PE文件格式”1.9版?完整譯文(附注釋))http://bbs.pediy.com/showthread.php?threadid=21932,
??????我們應(yīng)該用IMAGE_THUNK_DATA結(jié)構(gòu)里面的AddressOfData來判斷。下面的代碼可行:
??????while(ThunkData->u1.AddressOfData!=NULL){
?????????ImportBN?=?(PIMAGE_IMPORT_BY_NAME)((DWORD)(ThunkData->u1.AddressOfData)?-?Offset?+(PBYTE)pMapping);?
???//顯示導(dǎo)入函數(shù)
???if(((DWORD)ThunkData->u1.AddressOfData?&?IMAGE_ORDINAL_FLAG32)?==?0){
????AddText(hEdit,TEXT("%03d:??%s\r\n"),i++,ImportBN->Name);
???}
???else{
????AddText(hEdit,TEXT("%03d:??Ord?by?Hint\r\n"),i++);
???}
???ThunkData?++;??
??????}//End?of?while
6??????導(dǎo)出表:
6.1????導(dǎo)出表的結(jié)構(gòu),
???typedef?struct?_IMAGE_EXPORT_DIRECTORY?{
??????DWORD???Characteristics;
??????DWORD???TimeDateStamp;
??????WORD????MajorVersion;
??????WORD????MinorVersion;
??????DWORD???Name;
??????DWORD???Base;
??????DWORD???NumberOfFunctions;
??????DWORD???NumberOfNames;
??????DWORD???AddressOfFunctions;?????//?RVA?from?base?of?image
??????DWORD???AddressOfNames;?????????//?RVA?from?base?of?image
??????DWORD???AddressOfNameOrdinals;??//?RVA?from?base?of?image
???}?IMAGE_EXPORT_DIRECTORY,?*PIMAGE_EXPORT_DIRECTORY;
6.2???AddressOfNames?和AddressOfNameOrdinals?是一一對應(yīng)的,只不過一個(gè)用于名字,
??????一個(gè)用于序號,?同一個(gè)函數(shù)的索引都相同。
6.3???NumberOfFunctions?–?NumberOfNames?應(yīng)該就是由序號引出的函數(shù)數(shù)目了
6.4???對于由序號導(dǎo)出的函數(shù),不知道有沒有辦法能通過序數(shù)找到函數(shù)名。個(gè)人考慮似乎不可能這樣
??????找函數(shù)名字,不然,微軟未公開的函數(shù)就都被我們通過函數(shù)序數(shù)枚舉出來了??:)
7:??把我的PE查看器修改了下,?原來的在處理用序號引出的函數(shù)時(shí)會出錯.:)
總結(jié)
- 上一篇: 使用互斥体使程序只运行一个
- 下一篇: Python入门--模块的导入和使用