PE结构导出表
PE體系
PE結構&整體敘述
PE結構&導入表
PE結構&導出表
PE結構&基址重定位表
PE結構&綁定導入實現
PE結構&延遲加載導入表
導出表的作用
代碼重用機制提供了重用代碼的動態鏈接庫,它會向調用者說明庫里的哪些函數是可以被別人使用的,這些用來說明的信息便組成了導出表。
通常情況下,導出表存在于動態鏈接庫文件里。但不能簡單地認為EXE中沒有導出表,例如WinWord.exe文件里就有;也不能簡單地認為所有的DLL中都有導出表,例如一些專門存放資源位文件的DLL里就沒有導出表。
它的存在可以讓程序開發者很容易清楚PE中到底有多少可以使用的函數,但如果沒有函數使用說明,開發著只能通過函數名稱,反匯編代碼或者運行結果對函數的調用方式,函數的功能等進行猜測。
Windows裝載器在進行PE裝載時,會將導入表中登記的所有DLL一并裝入,然后根據DLL的導出表中對導入函數的描述修正導入表的IAT值。通過導入表,DLL文件向調用它的程序或系統提供導出函數的名稱,序號,以及入口地址等信息。
綜上所述,作用如下:
導出表的定位
導出表數據為數據目錄中注冊的數據類型之一,其描述信息處于數據目錄的第1個目錄項中如下:
0x2140轉換成文件偏移后是0x940,
導出目錄IMAGE_EXPORT_DIRECTORY
導出數據的第一個結構是IMAGE_EXPORT_DIRECTORY。該結構詳細定義如下:
導入表的IMAGE_IMPORT_DESCRIPTOR個數與調用的動態鏈接庫個數相等,而導出表的IMAGE_EXPORT_DIRECTORY只有一個。解釋如下:
IMAGE_EXPORT_DIRECTORY.nName
+000ch,雙字,該字段指示的地址指向了一個以“\0”結尾的字符串,字符串記錄了導出表所在的文件的最初文件名
IMAGE_EXPORT_DIRECTORY.NumberOfFunctions
+0014h,雙字。該字段定義了文件中導出函數的總個數
IMAGE_EXPORT_DIRECTORY.NumberOfNames
+0018h,雙字。在導出表中,有些函數是定義名字的,有些事沒有定義名字的。該字段記錄了所有定義名字函數的個數。如果此值為0,則表示所有的函數都沒有定義名字。NumberOfNames和NumberOfFunctions的關系是前者小于后者。
IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
+001ch,雙字。該指針指向了全部導出函數的入口地址的起始。從入口地址開始為雙字數組,數組的個數由字段IMAGE_EXPORT_DIRECTORY.NumberOfFunctions決定。導出函數的每一個地址按函數的編號順序依次往后排開。
IMAGE_EXPORT_DIRECTORY.nBase
+0010h,雙字。導出函數編號的起始值。DLL中的第一個導出函數并不從0開始的,某個導出函數的編號等于從AddressOfFunctions開始的順序號加上這個值,如下:
如上圖,Fun1的函數編號為nBase+0=200h,Fun2的函數編號為nBase+1=201h
IMAGE_EXPORT_DIRECTORY.AddressOfNames
+0020h,雙字,該值為一個指針,該指針指向的位置是一連串的雙字值,這些雙字值均指向了對應的定義了函數名的函數的字符串地址。這一連串的雙字個數為NumberOfNames.
IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals
+0024h,雙字。該值也是一個指針,與AddressOfNames是一一對應關系(注意,是一一對應),所不同的是,AddressOfNames指向的是字符串的指針數組,而AddressOfNameOrdinals則指向了該函數在AddressOfFunction中的索引值。
注意:
索引值是一個字,而非雙字。該值與函數編號是兩個不同的概念,兩者的之間的關系為:索引值=編號-nBase
導入表中“Hint/Name”中的Hint值是AddressOfFunctions 的索引值,并非編號。
結構圖:
0x940
對應IMAGE_EXPORT_DIRECTORY.nName字段,轉換成文件偏移后0x990
此字符串為winresult.dll,是動態鏈接庫的最初的名字
01 00 00 00對應IMAGE_EXPORT_DIRECTORY.nBase字段,表示起始編號為1
04 00 00 00對應IMAGE_EXPORT_DIRECTORY.NumberOfFunctions字段,表示共有4個導出函數
04 00 00 00對應IMAGE_EXPORT_DIRECTORY.NumberOfNames字段,表示4個導出函數均為按名稱導出
68 21 00 00轉換為文件偏移是0x968
對應IMAGE_EXPORT_DIRECTORY.AddressOfFunctions字段。從該位置取出連續4個地址(個數由IMAGE_EXPORT_DIRECTORY.NumberOfFunctions字段決定),這些地址分別對應4個函數的RVA
索引0:0x00001183
索引1:0x00001022
索引2:0x00001082
索引3:0x00001323
用圖表示:
轉換為文件偏移為0x978
對應IMAGE_EXPORT_DIRECTORY.AddressOfNames字段。從該位置取出的連續4個地址依次為
0x0000219e—>‘AnimateClose’\0
0x000021ab—>‘AnimateOpen’\0
0x000021b7—>‘FadeInOpen’\0
0x000021c2—>‘FadeOutClose’\0
轉換為文件偏移為0x988
對應IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals字段。從該位置取出的連續4個單字索引依次為:
0x0000
0x0001
0x0002
0x0003
這些索引的值存在于字段IMAGE_EXPORT_DIRECTORY.AddressOfFunctions所指向的函數地址列表中,最終的4個函數的編號將分別是此處的索引值加上nBase的值,函數的索引值可以在調用了該動態鏈接庫的程序FirstWindows.exe的導入表數據中找到
根據編號查找函數地址:
提示:
不建議使用編號查找函數地址。因為有很多的動態鏈接庫匯總標識的編號與對應的函數并不一致,通過這種方法找到的函數地址往往是錯誤的
根據名字查找函數地址:
舉例:
從庫中獲得函數地址的API為GetProcAddress()函數,該API引用EAT來獲取指定API的地址。其過程大致如下:
kernel32.dll中所有到處函數均有相應名稱,AddressOfNameOrdinals數組的值以index=ordinal的形式存在。但存在一部分dll中的導出函數沒有名稱,所以僅通過ordinal導出,從Ordinal值中減去IMAGE_EXPORT_DIRECTORY.Base 成員后得到一個值,使用該值作為“函數地址數組”的索引即可查找到相應函數的地址
導出表的應用
導出函數的覆蓋
導出表編程中常見的技術是,不需要修改用戶程序,便能將用戶程序中調用的動態鏈接庫函數轉向或者實施代碼覆蓋,實現用戶程序的調用轉移。(這種技術在殺毒軟件對用戶程序防護過程中,針對這種滲透是無效的)
1. 修改導出結構中的函數地址
直接利用二進制工具將 AddressOfFunctions索引1和2的地址(分別對應函數AnimateOpen和FadeInOpen)交換位置。
僅通過函數調用RVA地址0x00001282和0x00001022交換位置,即可實現導出函數的覆蓋。
需要注意的是,在使用導出函數地址覆蓋技術的時候,首先保證所涉及的兩個函數參數入口要一致,否則調用完成后棧不平衡。這將會導致應用程序調用失敗;其次,要求用戶對兩個函數的內部實現要有充分了解,使得地址轉向后,能夠保證應用程序在功能上可以全面兼容并允許良好
注意:
這種操作在實際操作中不贊成大家使用。
覆蓋函數地址部分的指令代碼
第二種常見的覆蓋技術,是將AddressOfFunctions指向的地址空間指令字節碼實施覆蓋。這種技術又衍生處兩種:
舉例:
導出私有函數
總結
- 上一篇: PE结构导入表
- 下一篇: PE结构基址重定位表