PE知识复习之PE的导出表
一丶簡介
在說明PE導出表之前.我們要理解.一個PE可執行程序.是由一個文件組成的嗎.
答案: 不是.是由很多PE文件組成.DLL也是PE文件.如果我們PE文件運行.那么就需要依賴DLL.系統DLL就是Kerner32.dll user32.dll等等.這些都是PE文件.
什么是導出表:
導出表就是當前的PE文件提供了那些函數.給別人用. 舉個例子: PE文件相當于一個飯店.那么菜單就是導出表.
導出表解盲:
有人認為exe可執行文件.沒有導出表.而DLL有導出表.這個是錯誤的. 不管是exe.還是DLL 本質都是PE文件. exe文件也可以導出函數給別人使用. 一般EXE沒有.但不是不可以有. 注意分清.
二丶導出表講解
在講解導出表之前.我們要確定導出表在哪里.
在講解擴展頭的時候.里面有一個結構體數組.我們稱之為數據目錄.里面有16項成員.
typedef struct _IMAGE_DATA_DIRECTORY {DWORD VirtualAddress; 虛擬地址DWORD Size; 大小 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;導入表.導出表都在數據目錄中存儲著.
這個結構存儲的是導出表在哪里.以及導出表有多大.
?其中數據目錄每一項都是保存著不同的表
例如第一項就是導出表. 記錄了導出表的虛擬地址 以及大小.
如下:
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor因為結構體記錄的是導出表的RVA. 所以我們需要轉換為FOA 去PE文件中查看.
RVA 判斷在那個節.? RVA-節.VirtuallAddress == 差值偏移
FOA == 差值偏移+ 節.PointerToRawData
?
前邊所說.是定位導出表在哪里. 定位之后.才是真正的導出表結構體.
typedef struct _IMAGE_EXPORT_DIRECTORY {DWORD Characteristics; // 不加紅的不重要DWORD TimeDateStamp; //時間戳. 編譯的時間. 把秒轉為時間.可以知道這個DLL是什么時候編譯出來的. WORD MajorVersion;WORD MinorVersion;DWORD Name; //指向該導出表文件名的字符串,也就是這個DLL的名稱 輔助信息.修改不影響 存儲的RVA 如果想在文件中查看.自己計算一下FOA即可.DWORD Base; // 導出函數的起始序號DWORD NumberOfFunctions; //所有的導出函數的個數DWORD NumberOfNames; //以名字導出的函數的個數DWORD AddressOfFunctions; // 導出的函數地址的 地址表 RVA 也就是 函數地址表 DWORD AddressOfNames; // 導出的函數名稱表的 RVA 也就是 函數名稱表DWORD AddressOfNameOrdinals; // 導出函數序號表的RVA 也就是 函數序號表 } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;一個導出表大小是 0x28個字節. 也就是兩行半.
其中重要成員都標紅了. 最重要的是導出表中最后三個成員.是三個子表.
都是RVA
PS: 數據目錄中的 Size成員.保存的是導出表中以及導出表子表中的所有成員大小. 這個值不影響.編譯器計算后填寫好的.
?
三丶導出表各成員解析
typedef struct _IMAGE_EXPORT_DIRECTORY {DWORD Characteristics; // 不加紅的不重要DWORD TimeDateStamp; //時間戳. 編譯的時間. 把秒轉為時間.可以知道這個DLL是什么時候編譯出來的. WORD MajorVersion;WORD MinorVersion;DWORD Name; //指向該導出表文件名的字符串,也就是這個DLL的名稱 輔助信息.修改不影響 存儲的RVA 如果想在文件中查看.自己計算一下FOA即可.DWORD Base; // 導出函數的起始序號DWORD NumberOfFunctions; //所有的導出函數的個數DWORD NumberOfNames; //以名字導出的函數的個數DWORD AddressOfFunctions; // 導出的函數地址的 地址表 RVA 也就是 函數地址表 DWORD AddressOfNames; // 導出的函數名稱表的 RVA 也就是 函數名稱表DWORD AddressOfNameOrdinals; // 導出函數序號表的RVA 也就是 函數序號表 } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;解析導出表.所需要的是一個DLL. 這里我拷貝一下系統的DLL kerner32.dll進行解析.
解析導出表的第一步就是定位導出表.求出FOA. 也就是在文件中的位置.
數據目錄中查看導出表RVA
在數據目錄中得出導出表RVA == 0x90380 大小 == D4DC
查看屬于那個節.求出FOA
?
得出在.rdata節中. 節.虛擬地址 == 0x80000? 節.文件偏移 == 0x65000
?
FOA = 0x90380 - 0x80000 + 0x65000 == 0x75380
所以在PE文件中.文件偏移 0x75380為導出表結構.
?
?
1.Name成員解析
?首先解析導出表重要的成員
Nmae: 在導出表一行位置處. 存儲0x9416A. 這是一個RVA 所以我們要進行FOA轉換. 這里直接計算了. FOA ==?7916A
?
可見這個成員保存的就是自己DLL的名稱.
2.Base成員解析.? 導出函數起始序號
?導出函數的序號起始位置. 你DLL導出的函數.如果給序號了.那么就從這個序號開始.?
3.NumberOfFunctions 以及 NumberOfNmaes? 函數導出總個數.以及函數以名字導出的個數
這個兩個成員很簡單. 一個就是所有函數導出的個數.一個就是以名字進行導出的個數.? DLL是可以以序號導出的.
?
所有函數導出是 62d個函數. 名字導出是 62d個函數. 如果有按照序號導出.那么以函數名導出的個數就會跟所有函數導出個數不一樣.
那么就有公式可以計算出. 未導出的函數是多少個.
所有導出函數個數 - 以名字導出的個數 == 差值個數. 未導出的.或者是以序號導出的.
4.1函數地址表
前面的都很簡單.下面的就是子表了.
?
三個子表都是RVA 我們直接都進行一下FOA轉換.
函數地址表 FOA == 0x753A8
函數名稱表 FOA == 0x76c5c
函數序號表 FOA == 0x78510
函數地址表:? 函數地址表指向一個偏移. 這個偏移存放了函數所有導出個數的 函數的地址.
例如所有導出函數有2個. 那么函數地址表中就有2項. 沒一個占4個字節. 存放的是函數地址的 RVA偏移.
?
函數地址表. 4個字節進行存儲. 總共有函數所有導出函數個數大小個字節. 例如第一項 RVA偏移為 0x0162A0 函數地址偏移 + ImageBase 就是函數地址.
?
例如我電腦上Kerner32.dll加載的Imagebase為 76360000? 我們在文件中看的函數偏移為 0x162A0? 相加就得出一個導出函數地址了 0x763762A0
PS: 因為我們在文件中查看導出表.所以一直在轉換FOA ,如果在內存中查看就很簡單了.
數據目錄的RVA + ImageBase 定位導出表位置.
導出表結構體中定義的RVA偏移+Imagebase就能得出其它表的位置.就不用我們進行轉換了.
還需要注意的就是,如果你按照序號導出. 1 3 4 5導出了4個函數. 在導入表中我們的函數地址表中的地址會有5個.原因就是.序號會給我們用0填充. 1 2 3 4 5 雖然第二項并沒有.但是也會給我們導出.
如果函數地址我們已經知道了.我們要怎么只有函數地址的情況下.確定是哪個函數?
4.2 函數名稱表
函數名稱表也是存儲的名稱RVA. 4個字節存儲一個. 存儲的大小 跟導出表的以函數名字導出個數 這個成員來決定的.
以名稱導出函數的個數 例如為10 .那么函數名稱表就可以存儲10個RVA. 每一個為4個字節.
里面的RVA指向了當前導出函數的函數名稱.
例如上面已經算出 函數地址表的FOA位置
函數名稱表 FOA == 0x76c5c
那么我們去函數名稱表中查看.
表中存儲的都是RVA. 如果在內存中.我們直接RVA + 當前PE的ImageBase就可以看到函數導出的名稱了.不過我們現在算一下.
FOA = 0x941D6? - 0x80000 + 0x65000 = 0x791D6
我們表中的第一項的FOA位置為0x791d6 在文件中就保存這導出函數的名稱
例如下圖文件偏移處:
注意: 函數名稱表保存的并不是函數名稱.而是指向函數名稱的RVA偏移. 還有RVA偏移是按照字母排序的.并不是按照你導出的時候函數的順序進行排序的.
例如:
EXPORT
SUB
ADD
MUL
導出三個函數.那么第一項就為 ADD.因為按照字母排序.A在前邊.后面依次類推. 所以我們上面看到的函數名稱 ACquireSRW 這個函數名稱.并不是Kerner32.dll第一個導出的函數.
4.3函數序號表
我們DLL導出函數的時候.會有序號進行導出.但是并不是說.如果按照名字導出名稱表中有.序號表中就沒有.
序號表的個數跟函數名稱表個數是一樣的.都依賴成員 導出表.函數名稱導出表個數 這個成員來決定的.
序號表是給名稱表的使用的.? 序號表占兩個字節.存儲序號.
函數序號表 FOA == 0x78510
?
0300? 0400 0500 序號.兩個字節進行存儲的
常用函數 GetProcAddress(模塊,名字或者序號)
我們這個函數就是遍歷PE文件中導出表進行返回的. 那么他是如何實現的.如何通過名字查找函數地址. 或者如何通過序號進行查找函數地址的?
首先我們要分成三張表,函數地址表中序號開始的位置是導出表成員Base指定的.假設為0開始.
函數地址表? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 序號表? ? ? ? ? ? ? ? ? ? ? ? ? ? 函數名稱表
0 0x1010? sub? ? ? ? ? ? ? ? ? ? ? ? ? 0? ? ? ? 0x0100? ? ? ? ? ? ? ? ? ? ? ? ?0? Add
1? ? ? 0x2020? Add? ? ? ? ? ? ? ? ? ? ? ? ? ?1? ? ? ?0x0000? ? ? ? ? ? ? ? ? ? ? ? ? 1? Sub
2 0x3030? Div? ? ? ? ? ? ? ? ? ? ? ? ? ? 2? ? ?0x0200? ? ? ? ? ? ? ? ? ? ? ? ? ? 2? DiV
首先GetProcAddress 如果按照名稱查找的話.會先去遍歷函數名稱表.? 比如我們要獲取Sub的地址.? 遍歷函數名稱表的時候.找到了Sub.? 并獲取當前Sub的索引.? sub是在第二項中.所以索引為1 (從0開始)
然后拿著這個索引.去序號表中進行查找對比.? ?在序號表中查到了.對比成功.序號表中第2項的值跟這個索引一樣的.所以就拿序號表的序號. 去函數地址表中獲取函數地址.
序號為0x0000. 那么他就在函數地址表中.找到了第0項. 當函數地址進行返回.? (并不是直接返回,加上了當前DLL模塊的ImageBase才返回的,所以為什么需要DLL模塊地址)?
所以上面就是GetProcAddress的名字查找的實現流程
?
如果是序號來查找的話.比如我們尋找 14序號. 他會先根據導出表中Base成員屬性.將表的起始位置進行一次定義.
例如上面.我們找的14序號并不存在. 但是他會先看看Base起始位置是多少. 假設為13. 那么我們函數地址表中 0索引 相當于 13? 1索引相當于 14? 2索引相當于15了.依次類推.
這樣我們雖然說尋找14. 但是根據Base起始位置的指定.那么也會尋找到我們的函數地址.
?
總結來說 :
1.遍歷函數名稱表 得出索引
2.當前索引.去序號表中查找.如果有.則取出當前序號表的序號.當做函數地址表的下標
3.得出下標. 返回函數地址 (RVA +IMAGEbase)
?
轉載于:https://www.cnblogs.com/gd-luojialin/p/11306189.html
總結
以上是生活随笔為你收集整理的PE知识复习之PE的导出表的全部內容,希望文章能夠幫你解決所遇到的問題。