由淺入深PE基礎學習-菜鳥手動查詢導出表、相對虛擬地址(RVA)與文件偏移地址轉換(FOA)


0 前言

此篇文章想寫如何通過工具手查導出表、PE文件代碼編程過程中的原理。文筆不是很好,內容也是查閱了很多的資料后整合出來的。希望借此加深對PE文件格式的理解,也希望可以對看雪論壇有所貢獻。因為了解PE文件格式知識點對於逆向破解還是病毒分析都是很重要的,且基於對PE文件格式的深入理解還可以延伸出更多非常有意思的攻防思維。另外看雪能支持Markdown真是太好了了!發主題更加方便了

1 導出表查詢工具

  • 1 ) dumpbin

VS自帶的工具,有很多的功能。但用來查詢程序的導出表也非常方便,使用例子如下:

dumpbin.exe /EXPORTS D:\PEDemo.dll

  • 2 ) DLL Export Viewer (DLL導出表查看工具)

一款免費的dll查看工具,可以幫助查看DLL鏈接庫文件中的輸出函數,COM類型庫及相應的偏移地址。

  • 3)010 Editor

十六進制編輯器010 Editor,有大量格式的解析模板,用EXE模板來解析二進制文件,輔助我們讀懂和編輯。

  • 4)LordPE

LordPE是查看PE格式文件信息的工具,並且可以修改相關信息。里面有個位置計算器的功能可以用於計算相對虛擬地址(RVA)轉換文件偏移地址(FOA)。

2 Windows導出表相關的結構

2.1 導出表所處位置

在Visual Studio里有一個名為WINNT.H的頭文件,里面定義了Windows系統里的PE內部結構。

 

PE結構中有一個NT頭(IMAGE_NT_HEADERS NtHeader),NT頭里包含了擴展頭(IMAGE_OPTIONAL_HEADER),擴展頭中包含數據目錄表(IMAGE_DATA_DIRECTORY_ARRAY)。

 

擴展頭定義:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _IMAGE_OPTIONAL_HEADER {
/ /
/ /  Standard fields.
/ /
 
...省略....
 
/ /
/ /  NT additional fields.
/ /
 
....省略...
 
      IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   / / 數據目錄表
} IMAGE_OPTIONAL_HEADER32,  * PIMAGE_OPTIONAL_HEADER32;

IMAGE_NUMBEROF_DIRECTORY_ENTRIES是個宏定義,值是0x16。

 

宏定義:

1
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16

數據目錄是一個有16個(WINNT.H中定義為IMAGE_NUMBEROF_DIRECTORY_ENTRIES)元素的結構數組。每個數組元素所指定的內容已經被預先定義好了。

 

WINNT.H文件中的這些IMAGE_DIRECTORY_ENTRY_xxx定義就是數據目錄的索引(從0到15)。導出表相對虛擬地址(RVA)就在數據目錄表中的第0個數組里。

 

數據目錄表有兩個結構體成員分別存有數據的相對虛擬地址和數據的大小,定義如下,:

1
2
3
4
5
6
7
typedef struct _IMAGE_DATA_DIRECTORY {
 
     DWORD   VirtualAddress;  / /  數據的相對虛擬地址(RVA)
 
     DWORD   Size;    / /  數據的大小
 
} IMAGE_DATA_DIRECTORY,  * PIMAGE_DATA_DIRECTORY;

下表描述了每個IMAGE_DIRECTORY_ENTRY_xxx值每個數組的意義。

 

image

 

圖1 IMAGE_OPTIONAL_HEADER中數據目錄表結構體數組含義

2.2 導出表結構

下面是導出表的數據結構定義說明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct _IMAGE_EXPORT_DIRECTORY {
 
    DWORD Characteristics;     / /  1 )  保留,恆為 0x00000000
 
    DWORD TimeDateStamp;       / /  2 )  時間戳,導出表創建的時間(GMT時間)
 
    WORD  MajorVersion;        / /  3 )  主版本號:導出表的主版本號
 
    WORD  MinorVersion;        / /  4 )  子版本號:導出表的子版本號
 
    DWORD Name;                / /  5 )  指向模塊名稱的RVA,指向模塊名(導出表所在模塊的名稱)的ASCII字符的RVA
 
    DWORD Base;                / /  6 )  導出表用於輸出導出函數序號值的基數: 導出函數序號  =  函數入口地址數組下標索引值  +  基數
 
    DWORD NumberOfFunctions;   / /  7 )  導出函數入口地址表的成員個數
 
    DWORD NumberOfNames;       / /  8 )  導出函數名稱表中的成員個數
 
    DWORD AddressOfFunctions;  / /  9 )  函數入口地址表的相對虛擬地址(RVA),每一個非 0 的項都對應一個被導出的函數名稱或導出序號(序號 + 基數等於導出函數序號)
 
    DWORD AddressOfNames;      / /  10 ) 函數名稱表的相對虛擬地址(RVA),存儲着指向導出函數名稱的ASCII字符的RVA
 
    DWORD AddressOfNameOrdinals;  / /  11 ) 存儲着函數入口地址表的數組下標索引值(序號表),跟導出函數名稱表的成員順序對應
 
} IMAGE_EXPORT_DIRECTORY, * PIMAGE_EXPORT_DIRECTORY;
  1. (沒用)Characteristics; 保留,恆為0x00000000
  2. (沒用)TimeDateStamp; 時間戳,導出表創建的時間(GMT時間)
  3. (沒用)MajorVersion; 主版本號:導出表的主版本號
  4. (沒用)MinorVersion; 子版本號:導出表的子版本號
  5. (有用)Name; 指向模塊名稱的RVA,指向模塊名(導出表所在模塊的名稱)的ASCII字符的RVA
  6. (有用)Base; 導出表用於輸出導出函數序號值的基數:函數入口地址數組下標索引值 = 導出函數序號-基數
  7. (有用)NumberOfFunctions; 導出函數入口地址表的成員個數
  8. (有用)NumberOfNames; 以函數名稱導出的成員個數
  9. (有用)AddressOfFunctions; 函數入口地址表的相對虛擬地址(RVA),每一個非0的項都對應一個被導出的函數名稱或導出序號(序號+基數等於導出函數序號)
  10. (有用)AddressOfNames; 函數名稱表的相對虛擬地址(RVA),存儲着指向導出函數名稱的ASCII字符的RVA
  11. (有用)AddressOfNameOrdinals; 存儲着函數入口地址表的數組下標索引值(序號表),跟導出函數名稱表的成員順序對應

3 手動操作

本文旨在用於科普,大牛們可能要見笑了。手動操作部分借助工具010 Editor、LordPE先搞清楚PE結構的內容。涉及到相對虛擬地址(RVA)轉換文件偏移地址(FOA)的地方都用LordPE自帶功能轉換。原理與代碼后續貼出

3.1 010 Editor + LordPE 查找導出表位置

010 Edito通過EXE模板解析PE結構方法如下:

菜單 --> Templates --> Edit Template List

 

 

圖2 模板使用

 

在線模板文件地址:

 

http://www.sweetscape.com/010editor/repository/templates/

保存-模板-打開模板-F5運行,010 Editor會出來一個小窗口。

 

 

圖3 010Editor多出來的小窗口

 

010 Editor EXE模板查看的順序如下:

1
2
3
4
5
6
7
1 )struct IMAGE_NT_HEADERS nt_headers[NT頭]
 
2 )struct IMAGE_OPTIONAL_HEADER32 OptionalHeader[可選頭]
 
3 )struct IMAGE_DATA_DIRECTORIES DataDirectory[目錄表]
 
4 )struct IMAGE_DATA_DIRECTORY Export[導出表]

下圖中內容里對應的是導出表結構體中的VirtualAddress(導出表相對虛擬地址)和Size(導出表數據大小)兩個數據結構體成員,010 Editor面板里藍色高亮出來的數據就是導出表的相對虛擬地址和數據大小,對應IMAGE_DATA_DIRECTORY export結構體。

 

 

圖4 利用010 Editor的EXE模板查看導出表兩個數據結構體成員VirtualAddress和Size

 

在內存中數據是以“小尾方式”存放,“小尾方式”存放是以字節為單位,按照數據類型長度,低數據位排放在內存的低端,高數據排放在內存的高端。如0x00007ED0在內存中會被存儲為D07E0000。通過上面的操作,我們已經知道導出表

 

對應IMAGE_DATA_DIRECTORY.VirtualAddress的相對虛擬地址是0x00007ED0

 

對應IMAGE_DATA_DIRECTORY.Size的大小是0x000001A4

 

這里的0x00007ED0是相對虛擬地址(RVA),用LordPE工具轉換文件偏移地址(FOA)方法如下:

 

使用LordPE【位置計算器】功能模塊-【PE編輯器】-【位置計算器】,將0x00007ED0(RVA)轉換文件偏移得到0x00006ED0

 

 

圖5 使用LordPE計算出文件偏移得到0x00006ED0

 

0x00006ED0指向導出表數據結構(IMAGE_EXPORT_DIRECTORY)的位置,010Editor解析如圖6所示,導出表數據結構具體的字段含義已經在《2.2 導出表結構》中注明。參照圖標注如下:

 

 

圖6 0x00006ED0指向位置為導出表數據,導出表結構體標注

 

其中比較有用的是

1
2
3
4
5
6
7
8
9
10
11
12
13
1 ) Name;  指向模塊名稱的RVA,指向模塊名(導出表所在模塊的名稱)的ASCII字符的RVA
 
2 ) Base;  導出表用於輸出導出函數序號值的基數:函數入口地址數組下標索引值  =  導出函數序號 - 基數
 
3 ) NumberOfFunctions; 導出函數入口地址表的成員個數
 
4 ) NumberOfNames; 以函數名稱導出的函數個數
 
5 ) AddressOfFunctions;函數入口地址表的相對虛擬地址(RVA),每一個非 0 的項都對應一個被導出的函數名稱或導出序號(序號 + 基數等於導出函數序號)
 
6 ) AddressOfNames;函數名稱表的相對虛擬地址(RVA),存儲着指向導出函數名稱的ASCII字符的RVA
 
7 ) AddressOfNameOrdinals; 存儲着函數入口地址表的數組下標索引值(序號表),跟導出函數名稱表的成員順序對應

導出表數據結構(IMAGE_EXPORT_DIRECTORY)的相對虛擬地址為:

1
2
3
4
5
6
7
8
9
10
11
12
13
IMAGE_EXPORT_DIRECTORY.Name   = =  0x00007F20
 
IMAGE_EXPORT_DIRECTORY.Base   = =  0x00000001
 
IMAGE_EXPORT_DIRECTORY.NumberOfFunctions  = =  0x00000004
 
IMAGE_EXPORT_DIRECTORY.NumberOfNames  = =  0x00000004
 
IMAGE_EXPORT_DIRECTORY.AddressOfFunctions = =  0x00007EF8
 
IMAGE_EXPORT_DIRECTORY.AddressOfNames = =  0x00007F08
 
IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals  = =  0x00007F18

通過以上數據可以知道導出表用於輸出API函數索引值的基數為0x00000001。所有導出函數中的成員個數有4個,以導出名稱導出的函數個數有4個。

 

接下來使用LordPE的【位置計算器】功能逐個計算出指向模塊函數名稱、函數地址表、函數名稱地址表、導出序列號的相對虛擬地址(RVA)得出文件偏移(FOA):

IMAGE_EXPORT_DIRECTORY.Name == 0x00007F20 轉換為 00006F20 //指向導出表文件名的字符串

IMAGE_EXPORT_DIRECTORY.AddressOfFunctions == 0x00007EF8 轉換為 00006EF8 //導出函數入口地址表

IMAGE_EXPORT_DIRECTORY.AddressOfNames == 0x00007F08 轉換為 00006F08 //導出函數名稱地址表

IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals == 0x00007F18 轉換為 00006F18 //導出函數名稱表位置對應,存儲着指向函數入口地址序號表的索引

3.1.1 IMAGE_EXPORT_DIRECTORY.Name

0x00007F20 轉換為 00006F20,指向導出表Name字段,內容存儲的是模塊函數名稱的ASCII字符,測試用的DLL名稱如下:

PEDemo.dll

 

 

圖7 指向導出文件名的字符串

3.1.2 IMAGE_EXPORT_DIRECTORY.AddressOfFunctions

0x00007EF8 轉換成文件偏移地址為 00006EF8,這個地址對應的結構體成員是AddressOfFunctions,AddressOfFunctions指向的是所有導出函數地址表的RVA地址,有多少個地址根據NumberOfFunctions的值得出。

 

再使用LordPE【位置計算器】功能通過RVA轉換為FOA,查看所有導出函數地址表,得到以下的導出函數地址:

1
2
3
4
5
6
7
000010E6   - - > 序號為[ 0 ]
 
00001087   - - > 序號為[ 1 ]
 
0000108C   - - > 序號為[ 2 ]
 
00009138   - - > 序號為[ 3 ]

 

圖8 IMAGE_EXPORT_DIRECTORY.AddressOfFunctions

3.1.3 IMAGE_EXPORT_DIRECTORY.AddressOfNames

0x00007F08 轉換成文件偏移地址為 00006F08,這個地址對應的結構體成員是AddressOfNames,這個成員表存儲的是導出函數名字RVA。

 

因為前面根據NumberOfFunctions的值已經知道這個DLL里有4個導出函數,然后利用010Editor定位到文件偏移處00006F08的位置。里面存儲的是4個導出函數名稱對應的RVA地址。根據各個RVA轉換成FOA。得到地址如下:

 

這里需要注意結構體里存儲的是存放導出函數名稱的RVA,要得到存儲的導出函數名稱的ASCII還要多轉換一層RVA。

 

 

圖9 存儲着導出函數名稱的RVA

 

RVA轉換FOA如下:

1
2
3
4
5
6
7
0x00007F2B  轉換FOA為  00006F2B
 
0x00007F37  轉換FOA為  00006F37
 
0x00007F44  轉換FOA為  00006F44
 
0x00007F51  轉換FOA為  00006F51

使用010Editor查看00006F2B 、00006F37 、00006F44、00006F51可以看到其實每個函數的名稱存放位置是連續的,使用00進行了隔斷。

 

指向導出函數名稱表RVA,AddressOfNames字段的值如下:

1
2
3
4
5
6
7
fnPEDemoFun  
 
fnPEDemoFunA
 
fnPEDemoFunB
 
nPEDemo

 

圖10 指向導出表文件名的字符串

 

然后導出函數名稱表存儲的其實是指向真實函數字符串的RVA地址,轉換前的對應關系如下圖。

 

 

圖11 導出函數名稱地址表

3.1.4 IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals

0x00007F18 轉換成文件偏移地址為 00006F18,這個地址對應的結構體成員是IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals。

 

 

圖12 序號表

 

這張表與導出函數名稱地址表的順序對應,存儲着指向函數入口地址表的索引值(序號表,起始值從0開始)。編程的時候為DWORD類型,但卻是WORD類型,因為只有2字節。對應的導出函數名稱關系如下:

1
2
3
4
5
6
7
fnPEDemoFun    - - - 00  00
 
fnPEDemoFunA   - - - 01  00
 
fnPEDemoFunB   - - - 02  00
 
nPEDemo        - - - 03  00

AddressOfFunctions為函數入口地址,AddressOfNames為導出函數名,AddressOfNameOrdinals存儲着函數入口地址表的數組下標索引值(序號表),用三張表的數據進行對比,結構就更清晰了,關系表如下:

 

 

圖13 序號對應關系

 

根據導出函數序號 = 函數入口地址序號 + 基數,已知Base基數數為1,計算如下:

1
2
3
4
5
6
7
000010E6   序號[ 0 +  基數[ 1 =  導出序號為[ 1 ]
 
00001087   序號[ 1 +  基數[ 1 =  導出序號為[ 2 ]
 
0000108C   序號[ 2 +  基數[ 1 =  導出序號為[ 3 ]
 
00009138   序號[ 3 +  基數[ 1 =  導出序號為[ 4 ]

函數名稱與函數入口地址表數組下標索引序號對應,計算如下:

1
2
3
4
5
6
7
fnPEDemoFun    - - - 00  00  - - - >對應函數入口地址[ 000010E6 ]
 
fnPEDemoFunA   - - - 01  00  - - - >對應函數入口地址[ 00001087 ]
 
fnPEDemoFunB   - - - 02  00  - - - >對應函數入口地址[ 0000108C ]
 
nPEDemo        - - - 03  00  - - - >對應函數入口地址[ 00009138 ]

3.1.5 小結

AddressOfNames中保存着一組RVA,每個RVA指向一個字符串,即導出的函數名。與導出的函數名對應的是AddressOfNameOrdinals中對應的項。AddressOfNameOrdinals是一張設計得非常巧妙的一張表,讓我們很方便的利用這張表按導出函數名稱查找對應函數入口地址和按函數入口地址查找對應導出函數名稱。

  • 按導出函數名稱查找對應函數入口地址

1、獲取已函數地址表的個數,NumberOfFunctions為4

 

2、獲取以名稱導出的函數個數,NumberOfNames為4

 

3、獲取函數名稱的地址

 

4、獲取函數序號表的值

 

5、導出函數名與AddressOfNameOrdinals(函數序號表)的順序對應

 

6、序號表存儲的序號值是函數入口地址表的數組下標索引值

 

例子:我們已經知道3個函數名稱Func1、Func2、Func3對應的序號值為0、2、3。那么函數地址表的第1、3、4的RVA(相對虛擬地址)就是有函數名稱導出的函數入口地址,因為數組下標索引值由0起始計算。

  • 按函數入口地址查找對應導出函數名稱

1、獲取已函數地址表的個數,NumberOfFunctions為4

 

2、獲取以名稱導出的函數個數,NumberOfNames為4

 

3、獲取函數名稱的地址

 

4、獲取函數序號表的值

 

5、導出函數名與AddressOfNameOrdinals(函數序號表)的順序相互對應

 

6、遍歷函數入口地址的數組索引值與AddressOfNameOrdinals(函數序號表)的實際內容值(非數組下標索引值)對比

 

7、如果函數入口地址的數組索引值與序號表內的存儲內容相同,那么與函數序號表數組索引值順序對應的函數名稱就是函數入口地址對應的導出函數名稱。

    • 得到導出序號

當函數是以序號方式導出的,查找的時候直接用函數入口地址序號加上基數(Base)就等於導出函數序號了。

 

逆向推回來,就是導出函數序號減去基數就得到函數入口地址(AddressOfFunctions)的順序了,也就是數組下標索引值。

 

【導出函數序號】 = 【函數入口地址序號】+【基數】

 

【函數入口地址序號】 = 【導出函數序號】-【基數】

 

參考圖:

 

 

圖14 AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals關系圖

3.2 PE工具查看

印證自己查詢的是不是正確,可以通過VS自帶的dumpbin工具或是 DLL Export Viewer這類工具查看。

dumpbin.exe查看如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
C:\Program Files (x86)\Microsoft Visual Studio  14.0 \VC\ bin >dumpbin.exe  / EXPORTS d:\PEDemo.dll
Microsoft (R) COFF / PE Dumper Version  14.00 . 24210.0
Copyright (C) Microsoft Corporation.   All  rights reserved.
 
 
Dump of  file  d:\PEDemo.dll
 
File  Type : DLL
 
   Section contains the following exports  for  PEDemo.dll
 
     00000000  characteristics
     59948A34  time date stamp Thu Aug  17  02 : 08 : 52  2017
         0.00  version
            1  ordinal base
            4  number of functions
            4  number of names
 
     ordinal hint RVA      name
 
           1     0  000010E6  fnPEDemoFun  =  @ILT + 225 (_fnPEDemoFun)
           2     1  00001087  fnPEDemoFunA  =  @ILT + 130 (_fnPEDemoFunA)
           3     2  0000108C  fnPEDemoFunB  =  @ILT + 135 (_fnPEDemoFunB)
           4     3  00009138  nPEDemo  =  _nPEDemo
 
   Summary
 
         1000  . 00cfg
         1000  .data
         1000  .gfids
         1000  .idata
         3000  .rdata
         1000  .reloc
         1000  .rsrc
         5000  .text

DLL Export Viewer (DLL導出表查看工具)


圖15 DLL導出表查看工具結果

4 相對虛擬地址(RVA)轉文件偏移(FOA)

前面的章節使用工具進行手動查看導出表,而這一節則是查閱了大量網上的文章然后匯總而成的筆記。希望可以給大家帶來一些幫助!

 

術語:

  • RVA:RVA 是相對虛擬地址(Relative Virtual Address)的縮寫,它是文件映射到內存中的“相對地址”。

  • FOA:FOA是文件偏移地址(File Offest Address)的縮寫,它是文件在磁盤上存放時相對文件開頭的偏移地址。

4.1 轉換公式

LordPE的位置偏移功能的確很方便,但始終還是要熟悉原理,才能寫出操作PE的代碼。

 

有一條公式可以幫助我們很方便的計算出文件偏移的位置。

1
文件偏移(磁盤文件的位置 FOA) = 相對虛擬地址(任意RVA) - 該區段相對虛擬地址(RVA) + 該區段的文件偏移(offset)

要轉換的相對虛擬地址會落在一個區段中是因為每個偏移,不管是在文件中,還是在內存中,它們距離區段開始位置的距離總是相等的。

 

該區段相對虛擬地址(RVA) <--對應--> IMAGE_SECTION_HEADER.VirtualAddress

 

該區段的文件偏移(offset) <--對應--> IMAGE_SECTION_HEADER.PointerToRawData

 

在寫代碼前我們首先弄清楚基本概念。

 

 

圖16 PE文件映射到虛擬內存

 

根據上圖看出,區段裝入內存之后的偏移與文件偏移是存在差異的。所以當我們進行文件偏移與虛擬內存地址之間換算時,首先要得出所轉換的地址在第幾區段內。每個區段的含義如下。

 

4.1.1 區段名稱約定

  • .text代碼段,此區段內的數據全部為代碼

  • .data可讀寫的數據段,此區段內存放全局變量或靜態變量

  • .rdata只讀數據區段

  • .idara導入數據區段,此區段內存放導入表信息

  • .edata導出數據區段,次區段內存放導出表信息

  • .rsrc 資源區段,此區段內存放應用程序會用到的所有資源,如圖標、菜單等

  • .bss未初始化數據

  • .crt此區段包含用於支持C++運行時庫(CRT)所添加的數據

  • .tls此區段包含用於支持通過_declspec(thread)聲明的線程局部存儲變量的數據

  • .reloc此區段包含重定位信息

  • .sdata此區段包含相對於可被全局指針定位的可讀寫數據

  • .srdata此區段包含相對於可被全局指針定位的只讀數據

  • .pdata此區段包含異常表

  • ....等

4.1.2 區段表結構

 

重新溫習區段表(IMAGE_SECTION_HEADER)結構,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
typedef struct _IMAGE_SECTION_HEADER {
 
     BYTEName[IMAGE_SIZEOF_SHORT_NAME];   / /  1 )區段名
 
     union {
 
         DWORD   PhysicalAddress;
 
         DWORD   VirtualSize;
 
     } Misc;                             / /  2 )區段大小
 
     DWORD   VirtualAddress;             / /  3 )區段的RVA地址
 
     DWORD   SizeOfRawData;              / /  4 )文件中的區段對齊大小
 
     DWORD   PointerToRawData;           / /  5 )區段在文件中的偏移
 
     DWORD   PointerToRelocations;       / /  6 )重定位的偏移(用於OBJ文件)
 
     DWORD   PointerToLinenumbers;       / /  7 )行號表的偏移(用於調試)
 
     WORDNumberOfRelocations;            / /  8 )重定位表項數量(用於OBJ文件)
 
     WORDNumberOfLinenumbers;            / /  9 )行號表項數量
 
     DWORD   Characteristics;            / /  10 )區段的屬性
 
} IMAGE_SECTION_HEADER,  * PIMAGE_SECTION_HEADER;

LordPE中的顯示關系

 

 

圖17 LordPE中的顯示關系

1
2
3
4
5
6
7
Voffset    = =  IMAGE_SECTION_HEADER.VirtualAddress    / / 區段的RVA地址
 
VSize      = =  IMAGE_SECTION_HEADER.Misc              / / 區段的物理地址大小
 
Roffset    = =  IMAGE_SECTION_HEADER.PointerToRawData  / / 區段在文件中的偏移
 
RSize      = =  IMAGE_SECTION_HEADER.SizeOfRawData     / / 文件中的區段對齊大小

代碼例子

 

RVA是不變的,對比RVA在哪個區段內。找到RVA所在區段,然后計算出這個RVA到區段在內存中的開始位置的距離。

 

編程思路:

  • 步驟1:循環掃描區塊表得出每個區塊在內存中的起始 RVA(根據IMAGE_SECTION_HEADER 中的VirtualAddress 字段),並根據區塊的大小(根據IMAGE_SECTION_HEADER 中的SizeOfRawData 字段)算出區塊的結束 RVA(兩者相加即可),最后判斷目標 RVA 是否落在該區塊內。

  • 步驟2:通過步驟1定位目標 RVA 處於具體的某個區塊中后,那么用目標 RVA 減去該區塊的起始 RVA ,這樣就能得到目標 RVA 相對於起始地址的偏移量 RVA2.

  • 步驟3:在區塊表中獲取該區塊在文件中所處的偏移地址(根據IMAGE_SECTION_HEADER 中的PointerToRawData 字段), 將這個偏移值加上步驟2得到的 RVA2 值,就得到了真正的文件偏移地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
/ /           RVA轉FOA函數                / /
/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
DWORD   RvaToOffset(const void *  pFileData, DWORD dwRva)
{
 
     / /  獲取DOS頭
     IMAGE_DOS_HEADER  * pDosHeader  =  (IMAGE_DOS_HEADER * )pFileData;
 
     / /  獲取NT頭
     IMAGE_NT_HEADERS  * pNtHeader  =  (IMAGE_NT_HEADERS * )((DWORD)pDosHeader  +  pDosHeader - >e_lfanew);
 
     / /  得到區段個數
     DWORD   dwSectionNumber  =  pNtHeader - >FileHeader.NumberOfSections;
 
     / /  得到區段
     IMAGE_SECTION_HEADER *  pSectionHeader  =  IMAGE_FIRST_SECTION(pNtHeader);
 
     / /  遍歷區段表,找到RVA所在的區段
     / *
     *  每個偏移,不管是在文件中,還是在內存中,它們距離區段開始位置的距離
     *  總是相等的。
     *  而且,區段表中,保存着兩個開始偏移:
     *   1.  文件中的開始偏移
     *   2.  內存中的開始偏移
     *  具體過程:
     *   找到RVA所在區段, 然后計算出這個RVA到區段在內存中的開始位置的距離。
     *   用這個距離加上區段在文件中的開始位置就得到文件偏移了
     * /
 
     for  ( int  =  0 ; i < dwSectionNumber;  + + i) {
 
         / /  區段的起始相對虛擬地址RVA
         DWORD dwSectionBeginRva  =  pSectionHeader[i].VirtualAddress
 
         / /  區塊的結束相對虛擬地址RVA  =  區段的RVA地址  +  文件中的區段對齊大小
         DWORD dwSectionEndRva  =  pSectionHeader[i].VirtualAddress  +  pSectionHeader[i].SizeOfRawData;
 
 
         / /  判斷RVA是否在當前的區段中
         if  (dwRva > =  dwSectionBeginRva
             && dwRva < =  dwSectionEndRva) {
 
             / /  計算出RVA對應的文件偏移
             / /  公式:文件偏移   =   RVA  -  區段的起始相對虛擬地址RVA  +  區段的起始文件偏移FOA
             / /  1.  要轉換的RVA  -  區段的起始相對虛擬地址RVA
             DWORD dwTemp  =  dwRva  -  pSectionHeader[i].VirtualAddress;
             / /  2.  加上區段的起始文件偏移FOA,dwOffset為FOA
             DWORD dwOffset  =  dwTemp  +  pSectionHeader[i].PointerToRawData;
             / /  3.  得到文件偏移FOA
             return  dwOffset;
         }
     }
 
     return  - 1 ;
}

4.2 讀取PE導出表函數

查詢代碼用了C++和Python兩種語言實現,由於我習慣寫很多注釋,就不去分塊進行代碼講解了,代碼如下:

 

C++實現代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/ /  2017_GetPE_IMAGE_EXPORT_DIRECTORY .cpp : 定義控制台應用程序的入口點。
/ /
 
#include "stdafx.h"
#include <windows.h>
 
 
/ / 獲取PE文件導出表
/ / 編程思路:
/ / 1 、打開文件,獲取文件句柄
/ / 2 、讀取文件到內存緩沖區
/ / 3 、獲取DOS頭結構
/ / 4 、獲取NT頭結構
/ / 5 、獲取擴展頭結構
/ / 6 、獲取數據目錄表
/ / 7 、獲取導出表
/ / 8 、封裝相對虛擬地址轉換文件偏移地址函數
 
 
 
/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
/ /           RVA轉FOA函數                / /
/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
DWORD   RvaToOffset(const void *  pFileData, DWORD dwRva)
{
 
     / /  獲取DOS頭
     IMAGE_DOS_HEADER  * pDosHeader  =  (IMAGE_DOS_HEADER * )pFileData;
 
     / /  獲取NT頭
     IMAGE_NT_HEADERS  * pNtHeader  =  (IMAGE_NT_HEADERS * )((DWORD)pDosHeader  +  pDosHeader - >e_lfanew);
 
     / /  得到區段個數
     DWORD   dwSectionNumber  =  pNtHeader - >FileHeader.NumberOfSections;
 
     / /  得到區段
     IMAGE_SECTION_HEADER *  pSectionHeader  =  IMAGE_FIRST_SECTION(pNtHeader);
 
     / /  遍歷區段表,找到RVA所在的區段
     / *
     *  每個偏移,不管是在文件中,還是在內存中,它們距離區段開始位置的距離
     *  總是相等的。
     *  而且,區段表中,保存着兩個開始偏移:
     *   1.  文件中的開始偏移
     *   2.  內存中的開始偏移
     *  具體過程:
     *   找到RVA所在區段, 然后計算出這個RVA到區段在內存中的開始位置的距離。
     *   用這個距離加上區段在文件中的開始位置就得到文件偏移了
     * /
 
     for  ( int  =  0 ; i < dwSectionNumber;  + + i) {
 
         / /  區段的起始相對虛擬地址RVA
         DWORD dwSectionBeginRva  =  pSectionHeader[i].VirtualAddress;
 
         / /  區塊的結束相對虛擬地址RVA  =  區段的RVA地址  +  文件中的區段對齊大小
         DWORD dwSectionEndRva  =  pSectionHeader[i].VirtualAddress  +  pSectionHeader[i].SizeOfRawData;
 
 
         / /  判斷RVA是否在當前的區段中
         if  (dwRva > =  dwSectionBeginRva
             && dwRva < =  dwSectionEndRva) {
 
             / /  計算出RVA對應的文件偏移
             / /  公式:文件偏移   =   RVA  -  區段的起始相對虛擬地址RVA  +  區段的起始文件偏移FOA
             / /  1.  要轉換的RVA  -  區段的起始相對虛擬地址RVA
             DWORD dwTemp  =  dwRva  -  pSectionHeader[i].VirtualAddress;
             / /  2.  加上區段的起始文件偏移FOA,dwOffset為FOA
             DWORD dwOffset  =  dwTemp  +  pSectionHeader[i].PointerToRawData;
             / /  3.  得到文件偏移FOA
             return  dwOffset;
         }
     }
 
     return  - 1 ;
}
 
 
int  main()
{
     / /  要解析的PE文件
     / / char dllPath[MAX_PATH]  =  "D:\\PEDemo.dll" ;
     / / char dllPath[MAX_PATH]  =  "D:\\PEDemofnPEDemoFunB@2fnPEDemoFunA@3.dll" ;
     char dllPath[MAX_PATH]  =  "D:\\PEDemofnPEDemoFunB@2NONAME.dll" ;
     / / char dllPath[MAX_PATH]  =  "D:\\PEDemofnPEDemoFunB@2private.dll" ;
 
 
     / /  讀取PE文件
     HANDLE hFile  =  INVALID_HANDLE_VALUE;
     hFile  =  CreateFileA(dllPath,    / / PE文件路徑
         GENERIC_READ,               / / 文件訪問的權限,通常用GENERIC_READ, GENERIC_WRITE
         FILE_SHARE_READ,            / / 打開文件的操作方式
         NULL,                       / / 安全描述符
         OPEN_EXISTING,              / / 對存在的文件采用的操作
         FILE_ATTRIBUTE_NORMAL,      / / 文件或設備屬性和標志
         NULL);                      / / 為創建的文件提供文件屬性和擴展屬性
 
     if  (hFile  = =  INVALID_HANDLE_VALUE)
     {
         printf( "文件不存在,或者被占用\n" );
         return  0 ;
     }
 
     / /  獲取文件大小
     DWORD dwFileSize  =  GetFileSize(hFile, NULL);
     / /  申請緩沖區保存文件內容
     BYTE *  pFileData  =  new BYTE[dwFileSize];
     DWORD dwRead  =  0 ;
     / /  將文件讀取到緩沖區
     ReadFile(hFile, pFileData, dwFileSize, &dwRead, NULL);
 
     / /  使用DOS頭結構指向緩沖區
     IMAGE_DOS_HEADER *  pDosHeader  =  (IMAGE_DOS_HEADER * )pFileData;
 
     / /  獲取IMAGE_NT_HEADERS,NT頭
     IMAGE_NT_HEADERS *  pNtHeader  =  (IMAGE_NT_HEADERS * )(pDosHeader - >e_lfanew  +  (DWORD)pFileData);
 
     / /  獲取IMAGE_OPTIONAL_HEADER32,擴展頭
     IMAGE_OPTIONAL_HEADER *  pOptionHeader  =  (IMAGE_OPTIONAL_HEADER * )&pNtHeader - >OptionalHeader;
 
     / /  獲取IMAGE_DATA_DIRECTORIES,數據目錄表
     IMAGE_DATA_DIRECTORY *  pDataDirectory  =  pOptionHeader - >DataDirectory;
 
     / /  獲取IMAGE_DATA_DIRECTORY.Export,數據目錄表.導出表
     DWORD dwExportTableRva  =  pDataDirectory[ 0 ].VirtualAddress;
 
     / /  RVA轉FOA,IMAGE_DATA_DIRECTORY.Export轉文件偏移
     DWORD dwExportTableOffset  =  RvaToOffset(pFileData, dwExportTableRva);
 
     / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
     / /  獲取導出表IMAGE_EXPORT_DIRECTORY結構體文件偏移位置,以下是結構體含義:
     / / IMAGE_EXPORT_DIRECTORY{
     / /     DWORD   Characteristics;         / /  1 )  保留,恆為 0x00000000
     / /     DWORD   TimeDateStamp;           / /  2 )  時間戳,導出表創建的時間(GMT時間)
     / /     WORD MajorVersion;               / /  3 )  主版本號:導出表的主版本號
     / /     WORDMinorVersion;                / /  4 )  子版本號:導出表的子版本號
     / /     DWORD   Name;                    / /  5 )  (有用)指向模塊名稱的RVA,指向模塊名(導出表所在模塊的名稱)的ASCII字符的RVA
     / /     DWORD   Base;                    / /  6 )  (有用)導出表用於輸出API函數索引值的基數(函數索引值 = 導出函數索引值 - 基數)
     / /     DWORD   NumberOfFunctions;       / /  7 )  (有用)EAT 導出地址表中的成員個數
     / /     DWORD   NumberOfNames;           / /  8 )  (有用)ENT 導出名稱表中的成員個數
     / /     DWORD   AddressOfFunctions;      / /  9 )  (有用)EAT 函數地址表的相對虛擬地址(RVA),每一個非 0 的項都對應一個被導出的函數名稱或序號
     / /     DWORD   AddressOfNames;          / /  10 ) (有用)ENT 函數名稱表的相對虛擬地址(RVA),每一個非 0 的項都對應一個被導出的函數地址或序號
     / /     DWORD  AddressOfNameOrdinals;    / /  11 ) (有用)指向導出函數序列號的數組,導出序號表的相對虛擬地址(RVA)
     / / } IMAGE_EXPORT_DIRECTORY,  * PIMAGE_EXPORT_DIRECTORY;
     / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
     IMAGE_EXPORT_DIRECTORY *  pExportTable  =  / * 換行 * /
         (IMAGE_EXPORT_DIRECTORY * )(dwExportTableOffset  +  (DWORD)pFileData);
 
     / / 輸出會用到的值
     printf( "IMAGE_EXPORT_DIRECTORY.Name:0x%08X\n" , pExportTable - >Name);                             / /  模塊名稱
     printf( "IMAGE_EXPORT_DIRECTORY.Base:0x%08X\n" , pExportTable - >Base);                             / /  函數索引值的基數
     printf( "IMAGE_EXPORT_DIRECTORY.NumberOfFunctions:0x%08X\n" , pExportTable - >NumberOfFunctions);   / /  導出地址表中的成員個數
     printf( "IMAGE_EXPORT_DIRECTORY.NumberOfNames:0x%08X\n" , pExportTable - >NumberOfNames);           / /  導出名稱表中的成員個數
     printf( "IMAGE_EXPORT_DIRECTORY.AddressOfFunctions:0x%08X\n" , pExportTable - >AddressOfFunctions); / /  函數地址表的相對虛擬地址(RVA)
     printf( "IMAGE_EXPORT_DIRECTORY.AddressOfNames:0x%08X\n" , pExportTable - >AddressOfNames);         / /  函數名稱表的相對虛擬地址(RVA)
     printf( "IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals:0x%08X\n" , pExportTable - >AddressOfNameOrdinals);  / /  指向導出函數序列號的數組
 
 
     / /  IMAGE_EXPORT_DIRECTORY.Name轉換為FOA,獲取指向導出表文件名的字符串
     DWORD dwNameOffset  =  RvaToOffset(pFileData, pExportTable - >Name);
 
     / /  IMAGE_EXPORT_DIRECTORY.Base轉換為FOA,獲取基數
     DWORD dwBaseOffset  =  RvaToOffset(pFileData, pExportTable - >Base);
 
     / /  IMAGE_EXPORT_DIRECTORY.AddressOfFunctions轉換為FOA,獲取導出函數地址表
     DWORD dwAddressOfFunctionsOffset  =  RvaToOffset(pFileData, pExportTable - >AddressOfFunctions);
 
     / /  IMAGE_EXPORT_DIRECTORY.AddressOfNames轉換為FOA,獲取導出函數名稱地址表
     DWORD dwAddressOfNamesOffset  =  RvaToOffset(pFileData, pExportTable - >AddressOfNames);
 
     / /  IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals轉換為FOA,獲取導出函數序號表
     DWORD dwAddressOfNameOrdinalsOffset  =  RvaToOffset(pFileData, pExportTable - >AddressOfNameOrdinals);
 
     / /   IMAGE_EXPORT_DIRECTORY.Name 指向導出表Name字段,內容存儲的是模塊函數名稱的ASCII字符
     char *  pDllName  =  (char * )(dwNameOffset  +  (DWORD)pFileData);
     printf( "\nDll_Name: %s\n" , pDllName);
 
     / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
     / /  把所有的導出的函數地址打印出來。
     / /  並且,如果是以名稱導出,則輸出該名稱
     / /  如果是以序號導出,則輸出該序號。
     / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
 
     / /  IMAGE_EXPORT_DIRECTORY.AddressOfFunctions指向導出函數地址表
     DWORD *  pAddressTable  =  / * 換行 * /
         (DWORD * )((DWORD)pFileData  +  dwAddressOfFunctionsOffset);
 
     / /  IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals指向導出函數序號表
     / /  WORD *  pOrdinalTable  = (WORD * )((DWORD)pFileData  +  RvaToOffset(pFileData, pExportTable - >AddressOfNameOrdinals));
     WORD *  pOrdinalTable  =  (WORD * )((DWORD)pFileData  +  dwAddressOfNameOrdinalsOffset);
 
     / /  導出函數名稱,需要再轉換一層RVA,才能得到函數名稱所在的位置
     DWORD *  pNameTable  = (DWORD * )((DWORD)pFileData  +  dwAddressOfNamesOffset);
 
     / /  判斷是以序號導出,還是以函數名導出
     BOOL  bIndexIsExist  =  FALSE;
 
     / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
     / / /  pExportTable - >NumberOfFunctions 對應了IMAGE_EXPORT_DIRECTORY.NumberOfFunctions  / / /
     / / /  導出地址表中的成員個數                                                          / / /
     / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
     for  ( int  =  0 ; i < pExportTable - >NumberOfFunctions;  + + i) {
 
         / /  打印虛序號、導出函數地址表(RVA)
         printf( "虛序號[%d] " , i);
         printf( "地址(RVA): %08X" , pAddressTable[i]);
 
         / /  判斷當前的這個地址是否是以名稱方式導出的
         / /  判斷依據:
         / /    序號表保存的是地址表的一個下標,這個下標記錄着
         / /    地址表中哪個地址是以名稱方式導出的。
         / /    如果當前的這個下標保存在序號表中,則說明這個地址
         / /    是一個名稱方式導出,如果這個下標在序號表中不存在,
         / /    則說明,這個地址不是一個名稱方式導出,而是以序號進行導出
         bIndexIsExist  =  FALSE;
 
 
         / /  以導出名稱導出的函數個數的數量循環
         int  nNameIndex  =  0 ;
         for  (; nNameIndex < pExportTable - >NumberOfNames;  + + nNameIndex) {
 
             / /  判斷地址表的下標是否存在於序號表中
             if  (i  = =  pOrdinalTable[nNameIndex]) {
                 bIndexIsExist  =  TRUE;
                 break ;
             }
         }
 
         / /  判斷如果bIndexIsExist為真就是函數名導出,否則以函數序號導出。
         / /  函數名要多轉換一層RVA
         if  (bIndexIsExist  = =  TRUE) {
 
             / /  得到名稱表中的RVA
             DWORD dwNameRva  =  pNameTable[nNameIndex];
 
             / /  將名稱Rva轉換成存有真實函數名稱的文件偏移
             char *  pFunName  =
                 (char * )((DWORD)pFileData  +  RvaToOffset(pFileData, dwNameRva));
 
             printf( " 函數名:【%s】\t" , pFunName);
             / /  i : 是地址表中的索引號,也就是一個虛序號
             / /  真正的序號  =  虛序號  +  序號基數
             printf( " 序號:【%d】 " , i  +  pExportTable - >Base);
         }
         / /  當沒有導出函數名稱,則是以序號進行導出使用
         if  (bIndexIsExist  = =  FALSE)
         {
 
             / /  判斷地址表當前索引到的袁術是否保存着地址
             if  (pAddressTable[i] ! =  0 ) {
 
                 printf( " 函數名:【-】\t" );
                 / /  i : 是地址表中的索引號,也就是一個虛序號
                 / /  真正的序號  =  虛序號  +  序號基數
                 printf( " 序號:【%d】" , i  +  pExportTable - >Base);
             }
         }
 
         printf( "\n" );
     }
 
     system( "pause" );
     return  0 ;
}

Python利用pefile模塊查詢導出函數,是不是很懵逼?就兩行代碼?

1
2
3
4
5
6
7
import  pefile
 
# 通過pefile模塊讀取PE文件
pe  =  pefile.PE( 'notepad.exe'
 
for  exp  in  pe.DIRECTORY_ENTRY_EXPORT.symbols:
     print  hex (pe.OPTIONAL_HEADER.ImageBase  +  exp.address), exp.name, exp.ordinal

附件說明

因為示例DLL序號表與函數入口地址表本身就是0、1、2、3對應,附件多給幾個DLL進行對比。以下是附件DLL的說明與dumpbin.exe結果對比

 

請留意DLL序號表對應關系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
【PEdemo.dll】
 
     NumberOfFunctions     0x00000004     unsigned  long
 
     NumberOfNames     0x00000004     unsigned  long
 
     Base     0x00000001     unsigned  long
 
 
  AddressOfFunctions   AddressOfNames   - >    name             AddressOfNameOrdinal 實際上為(WORD)
     E6  10  00  00         2B  7F  00  00        fnPEDemoFun              00  00
     87  10  00  00         37  7F  00  00        fnPEDemoFunA             01  00
     8C  10  00  00         44  7F  00  00        fnPEDemoFunB             02  00
     38  91  00  00         51  7F  00  00        nPEDemo                  03  00
 
     dumpbin result:
          0.00  version
            1  ordinal base
            4  number of functions
            4  number of names
 
     ordinal hint RVA      name
 
           1     0  000010E6  fnPEDemoFun
           2     1  00001087  fnPEDemoFunA
           3     2  0000108C  fnPEDemoFunB
           4     3  00009138  nPEDemo
 
 
【PEDemofnPEDemoFunB@ 2fnPEDemoFunA @ 3.dll (指定序號)】
 
     NumberOfFunctions     0x00000004     unsigned  long
 
     NumberOfNames     0x00000004     unsigned  long
 
     Base     0x00000002     unsigned  long
 
 
  AddressOfFunctions   AddressOfNames   - >    name             AddressOfNameOrdinal 實際上為(WORD)
     8C  10  00  00        2B  7F  00  00         fnPEDemoFun              02  00
     87  10  00  00        37  7F  00  00         fnPEDemoFunA             01  00
     E6  10  00  00        44  7F  00  00         fnPEDemoFunB             00  00
     38  91  00  00        51  7F  00  00         nPEDemo                  03  00
 
 
   dumpbin result:
        0.00  version
            2  ordinal base
            4  number of functions
            4  number of names
 
     ordinal hint RVA      name
 
           4     0  000010E6  fnPEDemoFun  =  @ILT + 225 (_fnPEDemoFun)
           3     1  00001087  fnPEDemoFunA  =  @ILT + 130 (_fnPEDemoFunA)
           2     2  0000108C  fnPEDemoFunB  =  @ILT + 135 (_fnPEDemoFunB)
           5     3  00009138  nPEDemo  =  _nPEDemo 
 
結論:先找函數名稱,查看序號,得到函數入口地址
 
 
【D:\\PEDemofnPEDemoFunB@ 2NONAME .dll(隱藏函數名以序號導出)】
 
      NumberOfFunctions     0x00000004     unsigned  long
 
      NumberOfNames     0x00000003     unsigned  long
 
      Base     0x00000002     unsigned  long
 
   AddressOfFunctions   AddressOfNames   - >    name             AddressOfNameOrdinal 實際上為(WORD)
     8C  10  00  00          25  7F  00  00        fnPEDemoFun              01  00 
     E6  10  00  00          31  7F  00  00        fnPEDemoFunA             02  00
     87  10  00  00          3E  7F  00  00        nPEDemo                  03  00
     38  91  00  00                                                  
 
   dumpbin result:
         0.00  version
            2  ordinal base
            4  number of functions
            3  number of names
 
     ordinal hint RVA      name
 
           3     0  000010E6  fnPEDemoFun
           4     1  00001087  fnPEDemoFunA
           5     2  00009138  nPEDemo
           2       0000108C  [NONAME]
 
 
結論:查詢函數入口地址的數組序號,序號加基數等於導出函數序號(ordinal)
 
 
 
【D:\\PEDemofnPEDemoFunB@ 2private .dll(private關鍵字)】
 
         NumberOfFunctions     0x00000004     unsigned  long
         NumberOfNames     0x00000004     unsigned  long
         Base     0x00000002     unsigned  long
 
   AddressOfFunctions   AddressOfNames   - >    name             AddressOfNameOrdinal 實際上為(WORD)
      8C  10  00  00          2B  7F  00  00        fnPEDemoFun               00  01
     E6  10  00  00          37  7F  00  00        fnPEDemoFunA              00  02
     87  10  00  00          44  7F  00  00        fnPEDemoFunB              00  00
     38  91  00  00          51  7F  00  00        nPEDemo                   00  03
 
   dumpbin result:
         0.00  version
            2  ordinal base
            4  number of functions
            4  number of names
 
     ordinal hint RVA      name
 
           3     0  000010E6  fnPEDemoFun
           4     1  00001087  fnPEDemoFunA
           2     2  0000108C  fnPEDemoFunB
           5     3  00009138  nPEDemo        
 
結論:遍歷函數入口地址得到序號,遍歷名稱個數獲取對應的序號,得出函數入口地址對應的函數名稱

5 參考

1、《黑客免殺攻防》-7.4

 

2、[原創]手查PE導出表

 

http://bbs.pediy.com/thread-205989.htm

 

3、Portable Executable

 

https://en.wikipedia.org/wiki/Portable_Executable

 

4、PE文件的相對虛擬地址(RVA)和文件偏移地址(FOA)的轉換

 

http://blog.csdn.net/zhao0811112157/article/details/42192881

 

5、導出表一課

 

http://edu.csdn.net/course/detail/3002/49415?auto_start=1

 

6、[原創]解析PE結構之-----導出表

 

http://bbs.pediy.com/thread-122632.htm

 

7、[原創]PE學習之手工解析導出表

 

http://bbs.pediy.com/thread-217229.htm

 

8、IMAGE_DATA_DIRECTORY 數據目錄詳解

 

http://www.nohacks.cn/post-58.html

 

9、Windows Pe 第三章 PE頭文件-EX-相關編程-2(RVA_FOA轉換)

 

http://blog.csdn.net/u013761036/article/details/52751721

 

10、the-export-directory

 

http://resources.infosecinstitute.com/the-export-directory/

 

11、PE文件結構詳解(三)PE導出表

 

http://blog.csdn.net/evileagle/article/details/12176797

 

12、WindowsPE 第五章 導出表

 

http://blog.csdn.net/u013761036/article/details/53241515

 

13、小甲魚PE詳解之區塊描述、對齊值以及RVA詳解

 

http://blog.csdn.net/hk_5788/article/details/48225007

 

14、15PB學習參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM