PE文件结构(七)

Last updated on October 7, 2023 pm

导出表

回顾

目前我们已经完成对PE基本结构的解析,了解PE各个头的主要成员,并基于对这些头的理解,对PE文件进行一些改动。如果完成之前的练习,相信你一定对PE文件头的组成已经有了深刻的认识,而且对于C语言指针的理解也一定深入一个层次(:

然而PE文件最为复杂结构还没有真正现身,即重定位表、导入表、导出表,这三张表比之前所有的加起来都还要复杂,它们也是PE文件能够正常运行的关键所在,接下来就慢慢深入探究吧

知识铺垫

在学习这几张表之前还需要一些个知识的铺垫

静态链接

当我们在写程序的时候,总会面临代码复用的问题,常见的解决办法是将相关函数进行一个封装单独放在一个文件里面,然后再在主工程里面使用一个头文件include进来

![](C:\Users\yongrin\Desktop\截屏2023-07-06 14.42.05.png)

那么这个时候会一个问题,就是别人也想用这个代码,或者其他工程也想用这些代码怎么办呢,可能会想说那我直接复制一份不就好了嘛。但显然这样不够优雅,有没有一种方式可以打包这些代码,然后其他程序直接使用呢?

答案当然是可以的,就是把这些代码打包成一个lib文件,具体操作可以看这篇文章。这里是使用DevC++创建的lib文件,当然也可以使用其他编译工具,但是我觉得DevC++是比较方便的。

这样就可以很方便的复用代码,并且别人也可以使用啦

动态链接

那静态链接可以如此方便的复用代码,有没有什么问题呢?比如说发现在lib文件里有一个函数写错了,这时候就不得不重新编译一个lib文件,然后对使用的此lib的程序都需要重新编译,这个在大的工程里面的还是代价还是很大的。

因为静态链接就相当于把lib文件里的函数一股脑全部怼进工程里面,与自己复制代码得到的程序没什么两样,编译完成后里面的函数就不会发生变化。这也是称其为静态链接的原因,也是因为不会发生变化,所以整个程序都需要重新编译。

那么有没有一种做法可以在不更改工程的同时,对其使用到的函数进行修正呢?动态链接就是解决此问题的,可以参考这篇文章制作一个动态链接库并导出其中函数使用。

IMAGE_DATA_DIRECTORY

可选头的最后一个成员是一个结构体数组,这个数组存储了各种表的的地址,包括导出表、导入表、重定位表等。每一个结构体都是相同的,包含两个成员

1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //相对虚拟地址
DWORD Size;      //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

第一个成员就是该表的地址,第二个成员就该表的大小,但实际上每一张表都有自己统计大小的方法,这个Size字段没有用处,可以被任意修改。

可以使用以下代码遍历整个结构体数组,并得到每一个表的RVA

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
#include<windows.h>
#include<stdio.h>
#include<malloc.h>

int Size(FILE* fp){
int size = 0;
fseek(fp,0,SEEK_END);
size = ftell(fp);
fseek(fp,0,SEEK_SET);
return size;
}

void Data_Directoy(char* Buffer)
{
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS32 NTHeader = NULL; //这里用了PIMAGE_NT_HEADERS32 对应于32位文件,必须使用
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER32)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);


DWORD* Data_Directory = (DWORD*)OptionalHeader->DataDirectory;
int number = OptionalHeader->NumberOfRvaAndSizes;

for (int i = 0; i < 16; i++)
{
if (i == 0)
printf("EXPORT Directory:\n");
if (i == 1)
printf("IMPORT Directory:\n");
if (i == 2)
printf("RESOURCE Directory:\n");
if (i == 3)
printf("EXCEPTION Directory:\n");
if (i == 4)
printf("SECURITY Directory:\n");
if (i == 5)
printf("BASERELOC Directory:\n");
if (i == 6)
printf("DEBUG Directory:\n");
if (i == 7)
printf("COPYRIGHT Directory:\n");
if (i == 8)
printf("GLOBALPTR Directory:\n");
if (i == 9)
printf("TLS Directory:\n");
if (i == 10)
printf("LOAD_CONFIG Directory:\n");
if (i ==11)
printf("BOUND_IMPORT Directory:\n");
if (i == 12)
printf("IAT Directory:\n");
if (i == 13)
printf("DELAY_IMPORT Directory:\n");
if (i == 14)
printf("COM_DESCRIPTOR Directory:\n");
if (i == 15)
printf("Reserved Directory:\n");
printf("RVA: %X\nSize: %X\n=====================", *(Data_Directory + 2 * i), *(Data_Directory + 2 * i + 1));
printf("\n");
}
}

int main(){
char* buffer;

FILE* fp = NULL;
errno_t err_1 = fopen_s(&fp, "C:\\Users\\yongrin\\Desktop\\PE_info.exe", "rb");

//将文件读取到程序中
int size = Size(fp);
buffer = (char*)malloc(size);
fread(buffer,size,1,fp);

Data_Directoy(buffer);
}

导出表

什么是导出表

一个exe中,可能会动态链接一些库,那么这些库就得告诉别的exe有什么函数可以供其使用,要告知别的exe这些函数相关信息,比如名字、地址等等,因此就需要一张表来记录这些信息,就相当于是一个指南,告诉别人我能提供什么函数,你可以怎样找到这些函数。IMAGE_DATA_DIRECTORY的第一个成员指向的就是导出表。

然而要注意的是,并不是DLL才有导出表,exe同样也可以有导出表,它也能提供函数给别的exe使用

导出表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;//没用
DWORD TimeDateStamp;//时间戳
WORD MajorVersion;//没用
WORD MinorVersion;//没用
DWORD Name;//指向导出表文件名 RVA -->FOA+FileBuff=char *name;
DWORD Base;//导出函数起始序号
DWORD NumberOfFunctions;//导出函数个数
DWORD NumberOfNames;//以名称导出函数个数
DWORD AddressOfFunctions;//导出函数地址表 RVA-->FOA +FileBuff
DWORD AddressOfNames; //导出函数名称表 // RVA from base of image
DWORD AddressOfNameOrdinals; //导出函数序号表 // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, * PIMAGE_EXPORT_DIRECTORY;
  • Name 存储指向自己文件名字的地址,是一个RVA

  • Base

    在创造dll的过程中,我们可以使用def文件来手动决定导出函数序号

    其中里面最小的那个序号就是Base。这里面的序号也可以是随意的无序的,序号后面,也可以再加一个NONAME,那么这个函数在导出的时候就不会有名字,只能通过序号来使用它。

  • NumberOfFunctions 导出函数个数,是指所有函数的个数,包括使用无名函数导出的

  • NumberOfNames 以名字导出函数个数

  • AddressOfFunctions:存储一个RVA地址,指向一张存有所有函数地址的表,个数由NumberOfFunctions决定,以下称为地址表

  • AddressOfNames:存储一个RVA地址,指向一张存有所有函数名字地址的表,个数由NumberOfNames 决定,以下称为名称地址表

  • AddressOfNameOrdinals:存储一个地址,指向一张存有所有函数序号的表,个数与AddressOfNames相同,以下称为序号表

NOTE:

  • NumberOfFunctions计算方式。之前提及,在使用def导出函数序号时,我们可以随意决定函数序号,而NumberOfFunctions就依赖这些序号计算。具体地,它会用最大的序号 - 最小的序号 + 1这个公式来计算。也就是说,NumberOfFunctions并不一定就是实际导出函数个数

  • 地址表与名称地址表大小不一定。通常情况下地址表会大于名称地址表,因为地址表是由所有函数地址,名称表只是以名字导出的函数。但也是也可以让两个函数名字指向同一个函数,这种情况下名称地址表就有可能大于地址表

  • 在导出函数的时候可以通过使用def文件的方式更改dll函数导出序号,但是def里的序号与序号表里序号并不是一样的,而是序号表的序号 = def的序号 + base

  • 使用名字找一个函数流程。现在要找一个名为“ADD”的函数,其流程如下

    • 遍历名称地址表,从中一个一个比对,找到对应的函数,得到名称地址表的索引
    • 使用相同索引在序号表里得到序号
    • 以得到的序号为索引,在地址表里得到函数地址
  • 使用序号找一个函数流程。现在要找一个序号10的函数,其流程如下

    • 直接用序号减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
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

void Export_Table(char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
NTHeader = (PIMAGE_NT_HEADERS)(Buffer + DosHeader->e_lfanew);
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);

DWORD Export_Table_RVA = OptionalHeader->DataDirectory[0].VirtualAddress;

if(Export_Table_RVA == 0){
printf("This PE File Has Not Export Table!\n");
return;
}
DWORD Export_Table_FOA = RVA_TO_FOA(Export_Table_RVA,Buffer);
PIMAGE_EXPORT_DIRECTORY _Export_Table = (PIMAGE_EXPORT_DIRECTORY)(Buffer + Export_Table_FOA);


printf("Characteristics: %#x\n", _Export_Table->Characteristics);
printf("TimeDateStamp: %#x\n", _Export_Table->TimeDateStamp);
printf("MajorVersion: %#x\n", _Export_Table->MajorVersion);
printf("MinorVersion: %#x\n", _Export_Table->MinorVersion);
printf("Name: %s\n", (Buffer + RVA_TO_FOA(_Export_Table->Name, Buffer)));
printf("Base: %#x\n", _Export_Table->Base);
printf("NumberOfFunctions: %#x\n", _Export_Table->NumberOfFunctions);
printf("NumberOfNames: %#x\n", _Export_Table->NumberOfNames);
printf("AddressOfFunctions: %#x\n", _Export_Table->AddressOfFunctions);
printf("AddressOfNames: %#x\n", _Export_Table->AddressOfNames);
printf("AddressOfNameOrdinals: %#x\n", _Export_Table->AddressOfNameOrdinals);

printf("==========函数地址表 AddressOfFuncitons==========\n");
DWORD* AddressOfFuncitons = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfFunctions,Buffer));
for(int i = 0; i < _Export_Table->NumberOfFunctions;i++){
printf("下标:%d 函数地址:%x\n", i , *(AddressOfFuncitons + i));
}

printf("==========序号表 AddressOfOrdinals==========\n");
WORD* AddressOfOrdinals = (WORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfNameOrdinals,Buffer));
for (int i = 0; i < _Export_Table->NumberOfNames; i++)
{
printf("下标:%d 序号:%x\n", i , *(AddressOfOrdinals + i));
}

printf("==========函数名称表 AddressOfNames==========\n");
DWORD* AddressOfNames = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfNames,Buffer));
for(int i = 0; i < _Export_Table->NumberOfNames;i++){
char* nameaddres = Buffer + RVA_TO_FOA(*(AddressOfNames + i),Buffer);
printf("下标:%d 函数名称:%s\n", i ,nameaddres);
}
}

根据函数名称找函数

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
void GetFuncAddrByName(char* Buffer,char* name){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
NTHeader = (PIMAGE_NT_HEADERS)(Buffer + DosHeader->e_lfanew);
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);

DWORD Export_Table_RVA = OptionalHeader->DataDirectory[0].VirtualAddress;

if(Export_Table_RVA == 0){
printf("This PE File Has Not Export Table!\n");
return ;
}
DWORD Export_Table_FOA = RVA_TO_FOA(Export_Table_RVA,Buffer);
PIMAGE_EXPORT_DIRECTORY _Export_Table = (PIMAGE_EXPORT_DIRECTORY)(Buffer + Export_Table_FOA);

//函数地址表
DWORD* AddressOfFuncitons = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfFunctions,Buffer));
//序号表
WORD* AddressOfOrdinals = (WORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfNameOrdinals,Buffer));
//函数名称表
DWORD* AddressOfNames = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfNames,Buffer));

for(int i = 0; i < _Export_Table->NumberOfNames;i++){
char* nameaddr = Buffer + RVA_TO_FOA(*(AddressOfNames + i),Buffer);
if(!strcmp(name,nameaddr)){
int ordinal = *(AddressOfOrdinals + i);
printf("%s found: %x\n",name,*(AddressOfFuncitons + ordinal));
return;
}
}

printf("Func Not Found\n");
}

根据函数序号找函数

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

void GetFuncAddrByOrd(char* Buffer,int Ord){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
NTHeader = (PIMAGE_NT_HEADERS)(Buffer + DosHeader->e_lfanew);
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);

DWORD Export_Table_RVA = OptionalHeader->DataDirectory[0].VirtualAddress;

if(Export_Table_RVA == 0){
printf("This PE File Has Not Export Table!\n");
return ;
}
DWORD Export_Table_FOA = RVA_TO_FOA(Export_Table_RVA,Buffer);
PIMAGE_EXPORT_DIRECTORY _Export_Table = (PIMAGE_EXPORT_DIRECTORY)(Buffer + Export_Table_FOA);

//函数地址表
DWORD* AddressOfFuncitons = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfFunctions,Buffer));
int base = _Export_Table->Base;
printf("base: %d\n",base);
printf("%d found: %x\n",Ord,*(AddressOfFuncitons + Ord-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
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
#include <windows.h>
#include <stdio.h>

DWORD Size(FILE* fp){
DWORD size = 0;
fseek(fp,0,SEEK_END);
size = ftell(fp);
fseek(fp,0,SEEK_SET);
return size;
}


DWORD RVA_TO_FOA(DWORD RVA,char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FileHeader = NULL;
PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);

if (RVA < OptionalHeader->SizeOfHeaders)
{
return RVA;
}

for(int i=0;i<FileHeader->NumberOfSections-1;i++){
if(RVA >= SectionHeader->VirtualAddress && RVA < (SectionHeader+1)->VirtualAddress){
DWORD offset = RVA - SectionHeader->VirtualAddress;
return SectionHeader->PointerToRawData + offset;
}
else{
SectionHeader +=1;
}
}

//最后一个节区
SectionHeader +=1;
DWORD offset = RVA - SectionHeader->VirtualAddress;
return SectionHeader->PointerToRawData + offset;
}

void GetFuncAddrByName(char* Buffer,char* name){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
NTHeader = (PIMAGE_NT_HEADERS)(Buffer + DosHeader->e_lfanew);
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);

DWORD Export_Table_RVA = OptionalHeader->DataDirectory[0].VirtualAddress;

if(Export_Table_RVA == 0){
printf("This PE File Has Not Export Table!\n");
return ;
}
DWORD Export_Table_FOA = RVA_TO_FOA(Export_Table_RVA,Buffer);
PIMAGE_EXPORT_DIRECTORY _Export_Table = (PIMAGE_EXPORT_DIRECTORY)(Buffer + Export_Table_FOA);

//函数地址表
DWORD* AddressOfFuncitons = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfFunctions,Buffer));
//序号表
WORD* AddressOfOrdinals = (WORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfNameOrdinals,Buffer));
//函数名称表
DWORD* AddressOfNames = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfNames,Buffer));

for(int i = 0; i < _Export_Table->NumberOfNames;i++){
char* nameaddr = Buffer + RVA_TO_FOA(*(AddressOfNames + i),Buffer);
if(!strcmp(name,nameaddr)){
int ordinal = *(AddressOfOrdinals + i);
printf("%s found: %x\n",name,*(AddressOfFuncitons + ordinal));
return;
}
}

printf("Func Not Found\n");
}


void GetFuncAddrByOrd(char* Buffer,int Ord){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
NTHeader = (PIMAGE_NT_HEADERS)(Buffer + DosHeader->e_lfanew);
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);

DWORD Export_Table_RVA = OptionalHeader->DataDirectory[0].VirtualAddress;

if(Export_Table_RVA == 0){
printf("This PE File Has Not Export Table!\n");
return ;
}
DWORD Export_Table_FOA = RVA_TO_FOA(Export_Table_RVA,Buffer);
PIMAGE_EXPORT_DIRECTORY _Export_Table = (PIMAGE_EXPORT_DIRECTORY)(Buffer + Export_Table_FOA);

//函数地址表
DWORD* AddressOfFuncitons = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfFunctions,Buffer));
int base = _Export_Table->Base;
printf("base: %d\n",base);
printf("%d found: %x\n",Ord,*(AddressOfFuncitons + Ord-base));
}


void Export_Table(char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
NTHeader = (PIMAGE_NT_HEADERS)(Buffer + DosHeader->e_lfanew);
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);

DWORD Export_Table_RVA = OptionalHeader->DataDirectory[0].VirtualAddress;

if(Export_Table_RVA == 0){
printf("This PE File Has Not Export Table!\n");
return;
}
DWORD Export_Table_FOA = RVA_TO_FOA(Export_Table_RVA,Buffer);
PIMAGE_EXPORT_DIRECTORY _Export_Table = (PIMAGE_EXPORT_DIRECTORY)(Buffer + Export_Table_FOA);


printf("Characteristics: %#x\n", _Export_Table->Characteristics);
printf("TimeDateStamp: %#x\n", _Export_Table->TimeDateStamp);
printf("MajorVersion: %#x\n", _Export_Table->MajorVersion);
printf("MinorVersion: %#x\n", _Export_Table->MinorVersion);
printf("Name: %s\n", (Buffer + RVA_TO_FOA(_Export_Table->Name, Buffer)));
printf("Base: %#x\n", _Export_Table->Base);
printf("NumberOfFunctions: %#x\n", _Export_Table->NumberOfFunctions);
printf("NumberOfNames: %#x\n", _Export_Table->NumberOfNames);
printf("AddressOfFunctions: %#x\n", _Export_Table->AddressOfFunctions);
printf("AddressOfNames: %#x\n", _Export_Table->AddressOfNames);
printf("AddressOfNameOrdinals: %#x\n", _Export_Table->AddressOfNameOrdinals);

printf("==========函数地址表 AddressOfFuncitons==========\n");
DWORD* AddressOfFuncitons = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfFunctions,Buffer));
for(int i = 0; i < _Export_Table->NumberOfFunctions;i++){
printf("下标:%d 函数地址:%x\n", i , *(AddressOfFuncitons + i));
}

printf("==========序号表 AddressOfOrdinals==========\n");
WORD* AddressOfOrdinals = (WORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfNameOrdinals,Buffer));
for (int i = 0; i < _Export_Table->NumberOfNames; i++)
{
printf("下标:%d 序号:%x\n", i , *(AddressOfOrdinals + i));
}

printf("==========函数名称表 AddressOfNames==========\n");
DWORD* AddressOfNames = (DWORD*)(Buffer + RVA_TO_FOA(_Export_Table->AddressOfNames,Buffer));
for(int i = 0; i < _Export_Table->NumberOfNames;i++){
char* nameaddres = Buffer + RVA_TO_FOA(*(AddressOfNames + i),Buffer);
printf("下标:%d 函数名称:%s\n", i ,nameaddres);
}
}

int main(){
char* buffer;

FILE* fp1 = NULL;
errno_t err_1 = fopen_s(&fp1, "C:\\Program Files\\Microsoft SQL Server Compact Edition\\v4.0\\sqlceme40.dll", "rb");

DWORD size = Size(fp1);
buffer = (char*)malloc(size);
fread(buffer,size,1,fp1);

char* func_name = "DllRelease";
int func_ord = 2;
Export_Table(buffer);
GetFuncAddrByName(buffer,func_name);
GetFuncAddrByOrd(buffer,func_ord);
}

NOTE:

  • 可以自己制作简单dll实验,也可以使用其他软件的dll
  • 实际上,GetFuncByNameGetFuncByOrd 就相当于实现了之前调用dll时使用的一个系统函数 GetProcAddress

总结

导出表位于IMAGE_DATA_DIRECTORY的第一项,主要有以下几个成员

​ DWORD Base;//导出函数起始序号
​ DWORD NumberOfFunctions; //导出函数个数
​ DWORD NumberOfNames; //以名称导出函数个数
​ DWORD AddressOfFunctions; //导出函数地址表
​ DWORD AddressOfNames; //导出函数名称表
​ DWORD AddressOfNameOrdinals; //导出函数序号表

  • Base 是导出序号里面最小的那个

  • NumberOfFuncitons = 最大导出序号 - 最小导出序号 + 1

  • NumberOfNames = 以名字导出函数个数

  • AddressOfFunctions,AddressOfNames,AddressOfNameOrdinals均是RVA,各自再指向一张表

    • AddressOfFunctions 指向所有函数地址表,这里面函数地址也是RVA,

    • AddressOfNames 指向函数名称表,里面存储的是函数名字地址的RVA

    • AddressOfNameOrdinals 指向函数序号

    • AddressOfFunctions 数量 = NumberOfFuncitons

    • AddressOfNames 数量 = AddressOfNameOrdinals 数量 = NumberOfNames

  • 使用名字找一个函数流程,其流程如下

    • 遍历名称地址表,从中一个一个比对,找到对应的函数,得到名称地址表的索引
    • 使用相同索引在序号表里得到序号
    • 以得到的序号为索引,在地址表里得到函数地址
  • 使用序号找一个函数流程,其流程如下

    • 直接用序号减base得到索引
    • 使用索引在地址表里得到函数地址

PE文件结构(七)
http://example.com/2023/07/07/PE_6/
Author
yring
Posted on
July 7, 2023
Licensed under