日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

PE知识复习之PE的导出表

發布時間:2025/3/15 编程问答 13 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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的导出表的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。