pe結(jié)構(gòu)參考模板_第1頁
pe結(jié)構(gòu)參考模板_第2頁
pe結(jié)構(gòu)參考模板_第3頁
pe結(jié)構(gòu)參考模板_第4頁
pe結(jié)構(gòu)參考模板_第5頁
已閱讀5頁,還剩5頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、輸入表可執(zhí)行文件使用來自于其他DLL的代碼或數(shù)據(jù)時,稱為輸入。當(dāng)PE文件裝載時,Windows加載器的工作之一就是定位所有被輸入函數(shù)和數(shù)據(jù),并且讓正在被裝載入的文件可以使用那些地址。這個過程通過PE文件的輸入表(Import Table)來完成的,輸入表中保存的是函數(shù)名和其駐留的DLL名等動態(tài)鏈接所需要的信息,輸入表在軟件外殼技術(shù)上的地位非常重要,我這里會重點(diǎn)講解的!當(dāng)應(yīng)用程序調(diào)用一個DLL的代碼和數(shù)據(jù)時,那它正在隱含鏈接到DLL,這個過程完全由Windows加載器完成,另外一種是運(yùn)行期的顯示鏈接,這意味著必須確定目標(biāo)DLL已經(jīng)被加載,然后尋找API地址,這幾乎總是通過調(diào)用LoadLibrar

2、y和GetProcAddress來完成的。當(dāng)隱含地鏈接一個API 時,類似LoadLibrary和GetProcAddress的代碼始終在執(zhí)行,只不過這是Windows裝載器自動完成的。裝載器還保證PE文件所需要的任何附加的DLL都已載入。在PE文件內(nèi),有一組數(shù)據(jù)結(jié)構(gòu),它們分別對應(yīng)著每個被輸入的DLL。每一個這樣結(jié)構(gòu)都給出了被輸入的DLL的名稱并指向一組函數(shù)指針。這組函數(shù)指針被稱為輸入地址表(Import Address Table)簡稱IAT,每一個被引入的API在IAT里都有它自己保留的位置,在那里它將被Windows加載器寫入輸入函數(shù)的地址,最后一點(diǎn)是特別重要的:一旦模塊被裝入,IAT中

3、包含所要調(diào)用輸入函數(shù)的地址。把所有輸入函數(shù)放在IAT中同一個地方是很有意義的,這樣無論代碼中多少次調(diào)用一個輸入函數(shù),都會通過IAT中的同一個函數(shù)指針來完成。調(diào)用輸入表函數(shù)方法:高效:CALL DWORD PTR 00402010直接調(diào)用00402010中的函數(shù),地址00402010h位于IAT里低效:CALL 00401164.:00401164 jmp dword ptr 00402010這種情況,CALL把控制權(quán)轉(zhuǎn)到一個子程序,子程序中的JMP指令跳轉(zhuǎn)到位于IAT中的00402010h。簡單的說它使用5個字節(jié)的額外代碼,并且由額外的JMP將花費(fèi)更多的時間去執(zhí)行。為什么要使用這種低效的方法?

4、因?yàn)榫幾g器無法區(qū)別輸入函數(shù)的調(diào)用與普通函數(shù)調(diào)用,對于每一個函數(shù)調(diào)用,編譯器使用同樣形式的CALL指令:CALL XXXXXXXXXXXXXXXX是一個由鏈接器填充的實(shí)際的地址。注意指令不是從函數(shù)指針而是代碼中實(shí)際地址而來的,為了因果平衡,鏈接器必須表示產(chǎn)生一塊代碼來取代XXXXXXXX,簡單位的方法是像上面那樣調(diào)用一個JMP Stub。我們可以通過使用修飾來優(yōu)化我們的低效調(diào)用方式,可以用修飾函數(shù)的_declspec(dllimport)來告訴編譯器,這個函數(shù)來自另一個DLL中,這樣編譯器就會產(chǎn)生這樣的指令:CALL DWORD PTR XXXXXXXX而不是CALL XXXXXXXX,編譯器將

5、給函數(shù)加上_imp_前綴,然后直接送給鏈接器,這樣可以直接把_imp_xxx送到IAT,就不需要JMP Stub了。下面簡單分析一個實(shí)例,看看是怎么回事?程序被執(zhí)行的時候是怎樣使用導(dǎo)入函數(shù)的呢?先寫個簡單的Hello World程序反匯編一把,看看調(diào)用導(dǎo)入函數(shù)的指令都是什么樣子的,需要反匯編的兩句源代碼如下(呵呵,這個代碼我就不寫了): invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK invoke ExitProcess,NULL當(dāng)使用W32Dasm反匯編以后,這兩句代碼變成了以下的指令,如圖所示:1 / 10反匯編后,對

6、MessageBox和ExitProcess函數(shù)的調(diào)用變成了對0040101A和00401020地址的調(diào)用,但是這兩個地址顯然是位于程序自身模塊而不是在DLL模塊中的,實(shí)際上,這是由編譯器在程序所有代碼的后面自動加上的Jmp dword ptr xxxxxxxx類型的指令,這個指令是一個間接尋址的跳轉(zhuǎn)指令,xxxxxxxx地址中存放的才是真正的導(dǎo)入函數(shù)的地址。在這個例子中,00402000地址處存放的就是ExitProcess函數(shù)的地址。那么在沒有裝載到內(nèi)存之前,PE文件中的00402000地址處的內(nèi)容是什么呢?用PEID工具查得結(jié)果如圖所示由于鏡像基址為00400000h,所以0040200

7、0h地址實(shí)際上處于RVA為2000h的地方,再看看各個節(jié)的虛擬地址,可以發(fā)現(xiàn)2000h開始的地方位于.rdata節(jié)內(nèi),而這個節(jié)的Raw_偏移項(xiàng)目為600h,也就是說00402000h地址內(nèi)容實(shí)際上對應(yīng)PE文件偏移600h處的數(shù)據(jù)。我們就看看文件0600h處的內(nèi)容是什么?用UE打開程序,如圖所示:查看的結(jié)果是00002076h,這顯然不會是內(nèi)存中的ExitProcess函數(shù)的地址,慢著!將它作為RVA看會怎么樣呢?查看節(jié)表可以發(fā)現(xiàn)RVA地址00002076h也處于.rdata節(jié)內(nèi),減去節(jié)的起始地址00002000h后得到這個RVA相對于節(jié)首的偏移是76h,也就是說它對應(yīng)文件0676h開始的地方,

8、接下來可以驚奇地發(fā)現(xiàn),0676h再過去兩個字節(jié)的內(nèi)容正是函數(shù)名字符串“ExitProcess”!這都有點(diǎn)搞糊涂了,Call ExitProcess指令被編譯成了Call aaaaaaaa類型的指令,而aaaaaaaa處的指令是Jmp dword ptr xxxxxxxx,而xxxxxxxx地址的地方只是一個似乎是指向函數(shù)名字符串的RVA地址,這一系列的指令顯然是無法正確執(zhí)行的!但如果告訴你,當(dāng)PE文件被裝載的時候,Windows裝載器會根據(jù)xxxxxxxx處的RVA得到函數(shù)名,再根據(jù)函數(shù)名在內(nèi)存中找到函數(shù)地址,并且用函數(shù)地址將xxxxxxxx處的內(nèi)容替換成真正的函數(shù)地址,那么所有的疑惑就迎刃而

9、解了。呵呵,這樣講解之后,你對輸入表是否有的更新的認(rèn)識呢?怎樣獲取輸入表呢?導(dǎo)入表的位置和大小可以從PE文件頭中IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)的數(shù)據(jù)目錄字段中獲取,對應(yīng)的項(xiàng)目是DataDirectory字段的第2個IMAGE_DATA_DIRECTORY結(jié)構(gòu)從IMAGE_DATA_DIRECTORY結(jié)構(gòu)的VirtualAddress字段得到的是導(dǎo)入表的RVA值,如果在內(nèi)存中查找導(dǎo)入表,那么將RVA值加上PE文件裝入的基址就是實(shí)際的地址;如果在PE文件中查找導(dǎo)入表,需要將RVA轉(zhuǎn)換成File Offset。導(dǎo)入表由一系列的IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)組成

10、,結(jié)構(gòu)的數(shù)量取決于程序要使用的DLL文件的數(shù)量,每個結(jié)構(gòu)對應(yīng)一個DLL文件,例如,如果一個PE文件從10個不同的DLL文件中引入了函數(shù),那么就存在10個IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)來描述這些DLL文件,在所有這些結(jié)構(gòu)的最后,由一個內(nèi)容全為0的IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)作為結(jié)束。每個被PE文件隱式地鏈接進(jìn)來的DLL都有一個IID。在這個數(shù)組中,沒有字段指出該結(jié)構(gòu)數(shù)組的項(xiàng)數(shù),但它的最后一個單元是NULL,可以由此計(jì)算出該數(shù)組的項(xiàng)數(shù)。IMAGE_IMPORT_DESCRIPTOR STRUCT union Characteristics dd ? Ori

11、ginalFirstThunk dd ? ends TimeDateStamp dd ? ForwarderChain dd ? Name1 dd ? FirstThunk dd ?IMAGE_IMPORT_DESCRIPTOR ENDS結(jié)構(gòu)中的Name1字段(使用Name1作為字段名同樣是因?yàn)镹ame一詞和MASM的關(guān)鍵字沖突)是一個RVA,它指向此結(jié)構(gòu)所對應(yīng)的DLL文件的名稱,這個文件名是一個以NULL結(jié)尾的字符串。OriginalFirstThunk字段和FirstThunk字段的含義現(xiàn)在可以看成是相同的(使用“現(xiàn)在”一詞的含義馬上會見分曉),它們都指向一個包含一系列IMAGE_THUN

12、K_DATA結(jié)構(gòu)的數(shù)組,數(shù)組中的每個IMAGE_THUNK_DATA結(jié)構(gòu)定義了一個導(dǎo)入函數(shù)的信息,數(shù)組的最后以一個內(nèi)容為0的IMAGE_THUNK_DATA結(jié)構(gòu)作為結(jié)束。一個IMAGE_THUNK_DATA結(jié)構(gòu)實(shí)際上就是一個雙字,之所以把它定義成結(jié)構(gòu),是因?yàn)樗诓煌臅r刻有不同的含義,結(jié)構(gòu)的定義如下:IMAGE_THUNK_DATA STRUCT union u1 ForwarderString dd ? Function dd ? Ordinal dd ? AddressOfData dd ? endsIMAGE_THUNK_DATA ENDS一個IMAGE_THUNK_DATA結(jié)構(gòu)如何用來

13、指定一個導(dǎo)入函數(shù)呢?當(dāng)雙字(就是指結(jié)構(gòu)?。┑淖罡呶粸?時,表示函數(shù)是以序號的方式導(dǎo)入的,這時雙字的低位就是函數(shù)的序號。讀者可以用預(yù)定義值IMAGE_ORDINAL_FLAG32(或80000000h)來對最高位進(jìn)行測試,當(dāng)雙字的最高位為0時,表示函數(shù)以字符串類型的函數(shù)名方式導(dǎo)入,這時雙字的值是一個RVA,指向一個用來定義導(dǎo)入函數(shù)名稱的IMAGE_IMPORT_BY_NAME結(jié)構(gòu),此結(jié)構(gòu)的定義如下:IMAGE_IMPORT_BY_NAME STRUCT Hint dw ? Name1 db ?IMAGE_IMPORT_BY_NAME ENDS結(jié)構(gòu)中的Hint字段也表示函數(shù)的序號,不過這個字段是可

14、選的,有些編譯器總是將它設(shè)置為0,Name1字段定義了導(dǎo)入函數(shù)的名稱字符串,這是一個以0為結(jié)尾的字符串。整個過程聽起來很復(fù)雜,其實(shí)看下面的圖示,可執(zhí)行文件導(dǎo)入了Kernel32.dll中的ExitProcess,ReadFile,WriteFile和lstcmp函數(shù)的情況,其中,前面3個函數(shù)按照名稱方式導(dǎo)入,最后lstrcmp函數(shù)按照序號導(dǎo)入,這四個函數(shù)分別是02f6h,0111h,002bh和0010h。導(dǎo)入表中IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)的Name1字段指向字符串“Kernel32.dll”,表明當(dāng)前要從Kernel32.dll文件中導(dǎo)入函數(shù),OriginalFirs

15、tThunk和FirstThunk字段指向兩個同樣的IMAGE_THUNK_DATA數(shù)組,由于要導(dǎo)入的是4個函數(shù),所以數(shù)組中包含4個有效項(xiàng)目并以最后一個內(nèi)容為0的項(xiàng)目作為結(jié)束。第4個函數(shù)lstrcmp函數(shù)是以序號導(dǎo)入的,與其對應(yīng)的IMAGE_THUNK_DATA結(jié)構(gòu)的最高位等于1,和函數(shù)的序號0010h組合起來的數(shù)值就是80000010h,其余的3個函數(shù)采用的是以函數(shù)名導(dǎo)入的方式,所以IMAGE_THUNK_DATA結(jié)構(gòu)的數(shù)值是一個RVA,分別指向3個IMAGE_IMPORT_BY_NAME結(jié)構(gòu),每個IMAGE_IMPORT_BY_NAME結(jié)構(gòu)的第一個字段是函數(shù)的序號,后面就是函數(shù)的字符串名稱

16、了,一切就是這么簡單!這里有個問題:為什么要用兩個并行的指針數(shù)組指向IMAGE_IMPORT_BY_NAME結(jié)構(gòu)呢?答安:當(dāng)PE文件被裝入內(nèi)存的時候,其中一個數(shù)組的值將被改作他用,還記得前面分析Hello World程序時提到的,Windows裝載器會將指令JMP DWORD PTR XXXXXXXX指定的XXXXXXXX處的RVA替換成真正的函數(shù)地址,其實(shí)XXXXXXXX地址正是由FirstThunk字段指向的那個數(shù)組中的一員。實(shí)際上,當(dāng)PE文件被裝載入內(nèi)存后,內(nèi)存中的映像就被Windows裝載器修正成了如下圖所示的樣子,其中由FirstThunk字段指向的那個數(shù)組中的每個雙字都被替換成了真

17、正的函數(shù)入口地址,之所以在PE文件中使用兩份IMAGE_THUNK_DATA數(shù)組的拷貝并修改其中的一份,是為了最后還可以留下一份拷貝用來反過來查詢地址所對應(yīng)的導(dǎo)入函數(shù)名。輸入表實(shí)例分析這里我就用老羅書上的那個FirstWindows作為實(shí)例進(jìn)行分析,因?yàn)榭囱┥夏莻€PE.EXE被我的360刪了,我也不知道,不管了,反正也要重新分析一遍,這樣更加深記憶!數(shù)據(jù)目錄表第二成員指向輸入表,該指針具體位置在PE文件頭的80h偏移處。該文件的PE文件頭起始位置是C0h,輸入表地址就是整個文件的C0h+80h=140h處,因此在140h處可以發(fā)現(xiàn)四字節(jié)指針90200000,倒過來是00002090,即輸入表在

18、內(nèi)存中偏移量是為2090h的地方,當(dāng)然,這個2090h是RVA值,需要將其轉(zhuǎn)換為磁盤文件的絕對偏移量,才能夠在十六進(jìn)制編輯器中找到輸入表。具體如下圖所示:大家可以通過計(jì)算來實(shí)現(xiàn)RVA到File Offset的轉(zhuǎn)換,這里我了節(jié)省篇幅,我就直接用LoadPE來計(jì)算了,如圖所示,我們要計(jì)算的RVA地址為00002090h,得到File Offset的地址為690h,如下圖所示:得到文件偏移地址之后,我們用UE打開FirstWindow程序,跳到偏移為690h處,這里就是輸入表的內(nèi)容,每個IID包含5個雙字,用來描述一個引入的DLL文件,最后以NULL結(jié)束。如圖所示:將圖中所列的輸入表的IID數(shù)組整理

19、到下面的表中。每個IID包含了一個DLL的描述信息,現(xiàn)在有兩個IID,因此這里引入了兩個DLL,第三個IID全為0,作為結(jié)束標(biāo)志。每個IID中的第四個字段是指向DLL名稱的指針,這里第一個IID中的第四個字段是0E220000,翻轉(zhuǎn)過來也就是RVA地址0000220Eh,用上面的方法轉(zhuǎn)換得到File Offset為80Eh,還有下面那個IID中的第四個字段也是指向DLL名稱的指,RVA地址為0000224Ch,轉(zhuǎn)換得到File Offset為84Ch,這樣我們就得到輸入表中所使用的兩個DLL的名稱,所圖所示:由上圖可知EXE文件中偏移量為80Eh處的字符是user32.dll,84Ch處的字符

20、是kernel32.dll,所以此程序調(diào)用了兩個DLL。上面的表格轉(zhuǎn)換成RVA地址,如下所示:再查找USER32.dll中被調(diào)用的函數(shù),在第一個IID中,查看第一個字段OrignalFirstThunk,它指向一個數(shù)據(jù),這個數(shù)組的元素都是指針,分別指向引入函數(shù)名的ASCII字符串。有些程序的OriginalFirstThunk的值為0,所以這時就要看FirstThunk,它在程序運(yùn)行時初始化。這時我就分析一個IID,第二個IID留著讀者自己去分析!USER32.DLL這個IID結(jié)構(gòu)中的OriginalFirstThunk的字段的值為000020DC,轉(zhuǎn)化為File Offset為:6DC,所以

21、在偏移6DCh處就是IMAGE_THUNK_DATA數(shù)組,它存儲的是指向IMAGE_IMPORT_BY_NAME結(jié)構(gòu)的地址,以一串00結(jié)束??傻玫饺缦卤硭镜腎MAGE_THUNK_DATA的數(shù)組。具體的位置如下圖所示:再來看看同一個IID結(jié)構(gòu)中FirstThunk情況,USER32.dll所在IID的FirstThunk字段值是2010h,然后轉(zhuǎn)換得到File Offset為610h,在偏移610h處就是IMAGE_THUNK_DATA數(shù)組,其數(shù)據(jù)與OrignalFirstThunk字段所指的完全一樣,如下圖所示:通常一個完整的程序就這些,現(xiàn)在有15個IMAGE_THUNK_DATA,表示有

22、15個函數(shù)調(diào)用,先選擇一個分析一下:4E210000翻轉(zhuǎn)后為0000214E,然后轉(zhuǎn)換為File Offset為74Eh,會發(fā)現(xiàn)在偏移74Eh處的字符串為DestroyWindow。你也許注意到了,計(jì)算出來的偏移量并不剛好指向函數(shù)名的ASCII字符串,而是前面還有兩個字節(jié)的空缺,這是作為函數(shù)名(Hint)引用的,可以為0。第一個IID指向的API函數(shù)表如下:如上圖是FirstWindow文件運(yùn)行前第一個IID的結(jié)構(gòu)示意圖,在程序運(yùn)行前,它的FirstThunk字段值是指向一個地址串,而且和OrignalFirstThunk字段值指向的INT是重復(fù)的,系統(tǒng)在程序初始化時根據(jù)OrignalFirs

23、tThunk的值找到函數(shù)名,調(diào)用GetProcAddress函數(shù)(或類似功能的系統(tǒng)代碼)且根據(jù)函數(shù)名取得函數(shù)的入口地址,然后用函數(shù)入口地址取代FirstThunk指向的地址串中對應(yīng)的值(IAT)。其內(nèi)部結(jié)構(gòu)如下圖所示,圖片來源加密與解密第三版下面利用加密與解密第三版上的實(shí)例dumped.exe講解PE文件映射到內(nèi)存的狀態(tài),找開映象文件,由于在內(nèi)存中區(qū)塊的對齊值與內(nèi)存頁相同,因此此時其文件偏移地址與相對虛擬地址(RVA)的值相等。輸入表的RVA地址是2040h,具體見下圖:由于00002040處的值為8C200000,翻轉(zhuǎn)為0000208C,再看208C處的IMAGE_THUNK_DATA和值為沒有映射到內(nèi)存中的是一樣的,但是FirstThunk的值為2010h,,該處指向的輸入表IAT,將這張表與沒有映射之前的比較,可以發(fā)現(xiàn)完全不同了。具體情況如下圖所示:內(nèi)存中第一個IID結(jié)構(gòu)的輸入地址表(IAT)表中各地址都是USER32.dl鏈連庫的相關(guān)輸出函數(shù),反匯編USER3

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論