23. PE结构-PE详解之输出表(导出表)
為每一個程序寫一個相同的函數(shù)看起來似乎有點浪費空間,因此Windows就整出了動態(tài)鏈接庫的概念,將一些常用的函數(shù)封裝成動態(tài)鏈接庫,等到需要的時候通過直接加載動態(tài)鏈接庫,將需要的函數(shù)整合到自身中,這樣就大大的節(jié)約了內(nèi)存中資源的存放。如圖:
有一個重要的概念需要記住:動態(tài)鏈接庫是被映射到其他應用程序的地址空間中執(zhí)行的,它和應用程序可以看成是“一體”的,動態(tài)鏈接庫可以使用應用程序的資源,它所擁有的資源也可以被應用程序使用,它的任何操作都是代表應用程序進行的,當動態(tài)鏈接庫進行打開文件、分配內(nèi)存和創(chuàng)建窗口等操作后,這些文件、內(nèi)存和窗口都是為應用程序所擁有的。
那導出表是干啥用的呢? 導出表就是記載著動態(tài)鏈接庫的一些導出信息。通過導出表,DLL 文件可以向系統(tǒng)提供導出函數(shù)的名稱、序號和入口地址等信息,比便Windows 加載器通過這些信息來完成動態(tài)連接的整個過程。
友情提示:擴展名為.exe 的PE 文件中一般不存在導出表,而大部分的.dll 文件中都包含導出表。但注意,這并不是絕對的。例如純粹用作資源的.dll 文件就不需要導出函數(shù)啦,另外有些特殊功能的.exe 文件也會存在導出函數(shù)。所以,世事無絕對……好了,我們接下來就對導出表的結構進行分析。
導出表結構
導出表(Export Table)中的主要成分是一個表格,內(nèi)含函數(shù)名稱、輸出序數(shù)等。序數(shù)是指定DLL 中某個函數(shù)的16位數(shù)字,在所指向的DLL 文件中是獨一無二的。在此我們不提倡僅僅通過序數(shù)來索引函數(shù)的方法,這樣會給DLL 文件的維護帶來問題。例如當DLL 文件一旦升級或修改就可能導致調(diào)用改DLL 的程序無法加載到需要的函數(shù)。
?
數(shù)據(jù)目錄表的第一個成員指向導出表,是一個IMAGE_EXPORT_DIRECTORY(以后簡稱IED)結構,IED 結構的定義如下:
IMAGE_EXPORT_DIRECTORY STRUCT
? ? ?Characteristics? ? ? ? DWORD?; 未使用,總是定義為0
? ? ?TimeDateStamp? ? ? DWORD? ? ? ? ; 文件生成時間
? ? ?MajorVersion? ? ? ? ? WORD? ; 未使用,總是定義為0
? ? ?MinorVersion? ? ? ? ? WORD? ; 未使用,總是定義為0
? ? ?Name? ? ? ? ? ? ? ? ? ? ? DWORD?; 模塊的真實名稱
? ? ?Base? ? ? ? ? ? ? ? ? ? ? ?DWORD?; 基數(shù),加上序數(shù)就是函數(shù)地址數(shù)組的索引值,一般為1
? ? ?NumberOfFunctions? ? ? ? ? ? ? DWORD?; 導出函數(shù)的總數(shù)(序號不推薦)
? ? ?NumberOfNames? ? ? ? ? ? ? ? ? DWORD? ; 以名稱方式導出的函數(shù)的總數(shù)
? ? ?AddressOfFunctions? ? ? ? ? ? ? DWORD?; 指向輸出函數(shù)地址的RVA
? ? ?AddressOfNames? ? ? ? ? ? ? ? ? DWORD?; 指向輸出函數(shù)名字的RVA
? ? ?AddressOfNameOrdinals? ? ? DWORD?; 指向輸出函數(shù)序號的RVA
IMAGE_EXPORT_DIRECTORY ENDS
這個結構中的一些字段并沒有被使用,有意義的字段說明如下。
- Name:一個RVA 值,指向一個定義了模塊名稱的字符串。如即使Kernel32.dll 文件被改名為"Ker.dll",仍然可以從這個字符串中的值得知其在編譯時的文件名是"Kernel32.dll"。
- NumberOfFunctions:文件中包含的導出函數(shù)的總數(shù)。
- NumberOfNames:被定義函數(shù)名稱的導出函數(shù)的總數(shù),顯然只有這個數(shù)量的函數(shù)既可以用函數(shù)名方式導出。也可以用序號方式導出,剩下 的NumberOfFunctions 減去NumberOfNames 數(shù)量的函數(shù)只能用序號方式導出。該字段的值只會小于或者等于 NumberOfFunctions 字段的值,如果這個值是0,表示所有的函數(shù)都是以序號方式導出的。(ring3 NumberOfNames[索引值] ==?0x10000 說明遍歷完成 ring0 MmIsAddressValid 判斷 模塊基地址+NumberOfNames[索引值] 是不是有效地址。不是。說明遍歷完成)
- AddressOfFunctions:一個RVA 值,指向包含全部導出函數(shù)入口地址的雙字數(shù)組。數(shù)組中的每一項是一個RVA 值,數(shù)組的項數(shù)等于NumberOfFunctions 字段的值。
- Base:導出函數(shù)序號的起始值,將AddressOfFunctions 字段指向的入口地址表的索引號加上這個起始值就是對應函數(shù)的導出 序號。假如Base 字段的值為x,那么入口地址表指定的第1個導出函數(shù)的序號就是x;第2個導出函數(shù)的序號就是x+1。總之,一個導出函數(shù)的導出序號等 于Base 字段的值加上其在入口地址表中的位置索引值。(經(jīng)過我的測試貌似不需要+Base 序號直接舊事AddressOfFunctions指向數(shù)組的索引值)
- AddressOfNames 和 AddressOfNameOrdinals:均為RVA 值。前者指向函數(shù)名字符串地址表。這個地址表是一個雙字數(shù)組,數(shù)組中的每一項指向一個函數(shù)名稱字符串的RVA。數(shù)組的項數(shù)等于NumberOfNames 字段的值,所有有名稱的導出函數(shù)的名稱字符串都定義在這個表中;后者指向另一個word 類型的數(shù)組(注意不是雙字數(shù)組)。數(shù)組項目與文件名地址表中的項目一一對應,項目值代表函數(shù)入口地址表的索引,這樣函 數(shù)名稱與函數(shù)入口地址關聯(lián)起來。(舉個例子說,加入函數(shù)名稱字符串地址表的第n 項指向一個字符串“MyFunction”,那么可以去查找 AddressOfNameOrdinals 指向的數(shù)組的第n 項,假如第n 項中存放的值是x,則表示AddressOfFunctions 字段描述的地址表中的第x 項函數(shù)入口地址對應的名稱就是“MyFunction”復雜吧? 沒事,接著看你就懂了,別放棄哦~)
整個流程跟其他PE 結構一樣說起來復雜,上圖:
1. 從序號查找函數(shù)入口地址
下邊帶大家來模擬一下Windows 裝載器查找導出函數(shù)入口地址的整個過程。如果已知函數(shù)的導出序號,如何得到函數(shù)的入口地址呢??
Windows 裝載器的工作步驟如下:
2. 從函數(shù)名稱查找入口地址
如果已知函數(shù)的名稱,如何得到函數(shù)的入口地址呢?與使用序號來獲取入口地址相比,這個過程要相對復雜一點!
Windows 裝載器的工作步驟如下:
?
一幫情況下病毒程序就是通過函數(shù)名稱查找入口地址的,因為病毒程序作為一段額外的代碼被附加到可執(zhí)行文件中的,如果病毒代碼中用到某些 API 的話,這些 API 的地址不可能在宿主文件的導出表中為病毒代碼準備好。因此只能通過在內(nèi)存中動態(tài)查找的方法來實現(xiàn)獲取API 的地址。
?
?
實踐:
第二個函數(shù)地址
總結
以上是生活随笔為你收集整理的23. PE结构-PE详解之输出表(导出表)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Chromium CEF 2623 --
- 下一篇: maltab求解微分方程