PE结构(4)导出表

Last updated on April 30, 2024 pm

导出表概览

所在位置

首先我们观察PE结构的OP头结构:

1
2
3
4
typedef struct _PE_HEADERS_OPTIONAL {
...
PE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBERSOF_DIRECTORY_ENTRY];
}PEHOPTIONAL, *lpPEHOPTIONAL;

在OP头结构的最后一个结构体数组中,存放所有数据目录页。IMAGE_NUMBERSOF_DIRECTORY_ENTRY的值固定为15。

数据目录页结构如下:

1
2
3
4
typedef struct _PE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
}PE_DATA_DIRECTORY, *lpPe_DATA_DIRECTORY;

其中,VirtualAddress是一个RVA, Size成员则基本没有意义。

RVA,即在虚拟内存中的相对地址,或者说是基于Imagebase的内存偏移量。
与之对应的概念是FOA,即基于文件起始地址的偏移量。
RVA与FOA之间存在转换关系。
PE结构(番外)RVA与FOA

通过VirtualAddress我们可以找到指定的数据目录页所指向的数据表位于文件或内存中的具体位置。

导出表的数据目录页,即数组DataDirectory的第一个元素,即*DataDirectory[0]*。

将该数据目录页的VirtualAddress成员读取,转换为FOA,则可以定位导出表在文件中的位置。

结构

导出表的数据结构如下(内存1字节对齐):

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _DATA_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base; //导出函数的最小序号
DWORD NumberOfFunctions; //导出函数个数,不准确。计算公式:最大序号 - 最小序号 + 1
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} DATA_EXPORT_DIRECTORY, * PDATA_EXPORT_DIRECTORY;

其中较为重要的当属:

  1. AddressOfFunctions : 函数表,存储导出函数地址(RVA),即四字节整数数组:DWORD[]
  2. AddressOfNames : 名称表,存储导出函数名字的地址(RVA),即四字节整数数组:DWORD[]
  3. AddressOfNameOrdinals : 序号表,存储导出函数序号(RVA),即两字节整数数组:WORD[]

需要注意的是,序号表中存储的并不是真正的导出序号。
而是 导出序号 - Base 的值。即每个函数的序号,相对于最小序号的偏移量。
我理解这是一种防溢出的手段,毕竟序号表是两字节整形数组,能表达的序号长度有限。
而实际导出函数,也不太可能一个文件导出几十万个函数来吧,所以存储偏移量就足够了。

获取导出函数的逻辑

已知函数的名称

只有以名称导出的函数,才会存在于AddressOfNames名称表中。

首先遍历名称表,获取指向名称的指针,而后比对名称是否一致。若一致,则从序号表中获取其序号,然后通过函数表获取函数地址。

伪代码逻辑:

1
2
3
4
5
6
7
8
9
10
11
String[] NameTable;//名称表
Short[] OrderTable;//序号表
Integer[] FuncTable;//函数表

String targetName = "target";
for(int i = 0; i < NameTable.size(); i++>){
if(NameTable[i] == targetName){
int funcIndex = OrderTable[i];
return FuncTable[funcIndex]
}
}

已知函数序号

若已知函数序号,则可以直接从函数表中获取函数。

直接将序号减去Base的值,然后从函数表中获取即可。

伪代码逻辑:

1
2
3
4
Integer Base = ExportTable.Base;
Integer[] FuncTable;//函数表
Integer order = 9;//已知函数导出序号
return FuncTable[9 - Base];

编程实现

主函数入口:

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
#include "PEStruct.h"
#include "PE.h"
#include <stdio.h>

void PrintExportTable(DATA_EXPORT_DIRECTORY table, lpPEFILE lpPEFile);

int main() {
//将指定文件加载到内存,并解析PE信息到PEFILE结构体
PEFILE peFile{ 0 };
FILE* pFile = fopen("D:\\TestDLL.dll", "rb");
int fileLength = LoadFileToMemory(pFile, &peFile);
void* pMemoryBuffer = nullptr;
fclose(pFile);

if (peFile.h_pe == nullptr) {
return 0;
}

PDATA_EXPORT_DIRECTORY lpExportTable = nullptr;
GetExportTable(&lpExportTable, &peFile);
if (lpExportTable == nullptr) {
return 0;
}

PrintExportTable(*lpExportTable, &peFile);
return 0;
}

打印导出表函数的实现:

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
void PrintExportTable(DATA_EXPORT_DIRECTORY table, lpPEFILE lpPEFile) {
DWORD imageBase = (DWORD)lpPEFile->h_dos;

char* name = (char*)(Rva2Foa(table.Name, lpPEFile) + imageBase);
WORD base = table.Base;
DWORD funcNum = table.NumberOfFunctions;
DWORD nameNum = table.NumberOfNames;
DWORD funcTableRva = table.AddressOfFunctions;
DWORD funcTableFoa = Rva2Foa(funcTableRva, lpPEFile) + imageBase;
DWORD nameTableRva = table.AddressOfNames;
DWORD nameTableFoa = Rva2Foa(nameTableRva, lpPEFile) + imageBase;
DWORD orderTableRva = table.AddressOfNameOrdinals;
DWORD orderTableFoa = Rva2Foa(orderTableRva, lpPEFile) + imageBase;

printf("导出表指向文件:%s\r\n", name);
printf("起始序号:%d\r\n", base);
printf("导出函数总数:%d, 名称导出总数:%d\r\n", funcNum, nameNum);
printf("函数表RVA:0x%x, FOA: 0x%x\r\n", funcTableRva, funcTableFoa);
printf("名称表RVA:0x%x, FOA: 0x%x\r\n", nameTableRva, nameTableFoa);
printf("序号表RVA:0x%x, FOA: 0x%x\r\n", orderTableRva, orderTableFoa);

DWORD* nameTable = (DWORD*)nameTableFoa;
WORD* orderTable = (WORD*)orderTableFoa;
DWORD* funcTable = (DWORD*)funcTableFoa;

for (WORD i = base; i <= funcNum - 1 + base; i++) {
if (funcTable[i-base] == NULL) {
continue;
}
WORD order = i;
char* name = nullptr;
DWORD funcRva = funcTable[i - base];
DWORD funcFoa = Rva2Foa(funcRva, lpPEFile);
DWORD funcAddr = funcFoa + imageBase;
//搜一下序号表,看能不能找到名字
for (DWORD j = 0; j < nameNum; j++) {
if (orderTable[j] != i-base) {
continue;
}
//能搜到,拿到名字
name = (char*)(Rva2Foa(nameTable[j], lpPEFile) + imageBase);
break;
}
if (name == nullptr) {
name = (char*)"NO_NAME";
}
printf("函数: %s 的序号是 %d, RVA: 0x%x, FOA: 0x%x 地址:0x%x \r\n",
name, order, funcRva, funcFoa, funcAddr);
}
}

PE结构(4)导出表
http://dubhehub.github.io/blogs/2024043014020040528.html
Author
Sin
Posted on
April 30, 2024
Licensed under