数据目录项

PIMAGE_OPTIONAL_HEADER结构下的最后两个成员NumberOfRvaAndSizes标识了目录项的数量(为16),IMAGE_DATA_DIRECTORY是一个数组,其每个数组成员标识了表的VA和大小,每一个模块都有一个IMAGE_DATA_DIRECTORY成员,结构如下

1
2
3
4
struct _IMAGE_DATA_DIRECTORY{
DWORD VirtualAddress;
DWORD Size; //编译器计算得出的每个表的大小,不影响程序运行
};

按顺序分别是:导出表、导入表、资源表、异常信息表、安全证书表、重定位表、调试信息表、版权所有表、全局指针表、TLS表、加载配置表、绑定导入表、IAT表、延迟导入表、COM信息表,最后一个保留未使用。

遍历目录项数据

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
#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<PETool.h>
#pragma comment(lib,"PETool.lib")
#define FILEPATH TEXT("D:\\absolution\\temporary\\test.exe")
#define OUTFILEPATH TEXT("D:\\absolution\\temporary\\output.exe")

int main() {
/*读取PE文件*/
LPVOID lpFileBuffer = createFileBuffer(FILEPATH);

if (!lpFileBuffer) {
return 0;
}

DWORD dwSize;
LPVOID lpImageBuffer = file2Image(lpFileBuffer, &dwSize);


PIMAGE_DOS_HEADER pDos =
(PIMAGE_DOS_HEADER)lpImageBuffer;
PIMAGE_NT_HEADERS pNT =
(PIMAGE_NT_HEADERS)((DWORD)lpImageBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFile =
(PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
PIMAGE_OPTIONAL_HEADER pOption =
(PIMAGE_OPTIONAL_HEADER)((DWORD)pFile + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER pFirstSection =
(PIMAGE_SECTION_HEADER)((DWORD)pOption + pFile->SizeOfOptionalHeader);

for (int i = 0; i < pOption->NumberOfRvaAndSizes; i++) {
IMAGE_DATA_DIRECTORY IDD = pOption->DataDirectory[i];
printf("IMAGE_DATA_DIRECTORY:%d \n%X\n%X\n---------\n", i, IDD.VirtualAddress, IDD.Size);
}

free(lpImageBuffer);

free(lpFileBuffer);

getchar();
}

导出表

导出表结构

1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
}

第一个成员指向的地址为导出表,结构如下,总共40个字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; //未使用
DWORD TimeDateStamp; //时间戳
WORD MajorVersion; //未使用
WORD MinorVersion; //未使用
DWORD Name; //!指向该导出表文件名字字符串
DWORD Base; //!导出函数起始序号
DWORD NumberOfFunctions; //!所有导出函数的个数
DWORD NumberOfNames; //!以函数名字导出的函数个数
DWORD AddressOfFunctions; //!导出函数地址表RVA
/*名称表里的数据依然是RVA*/
DWORD AddressOfNames; //!导出函数名称表RVA
DWORD AddressOfNameOrdinals; //!导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

NumberOfFunctions是通过导出序号计算出来的,如果序号有断档函数个数也会增加,序号不一定是连续的整数,序号最大的数 - 最小的数 + 1得到NumberOfFunctions

名称表和地址表地址指向的数据占4字节,序号表占2字节。


GetProcAddress如何找到函数地址

  1. 名字导出
    GetProcAddress函数调用dll时,如果传入函数名则会检索函数名称表,然后通过在函数名称表中的顺序(下标)在序号表中寻找,通过序号表找到的序号(作为下标)在地址表中检索

  2. 序号导出
    序号导出只需要用到地址表,导出表的Base成员标识了导出函数的起始序号,序号表中的值 + Base 得到真正的导出序号。如果传入序号则直接在地址表中检索到函数地址:导出序号 - Base 得到地址表的下标
    所以实际上导出函数里NONAME的函数是不计算在名称表和序号表的,序号表只是给名称表提供下标用的

综上所诉,如果导出序号不是连续的,地址表中会用0填充以保证使用下标可以检索到函数地址


遍历导出表

通过程序遍历导出表的练习,并编写函数模拟GetProcAddress的功能实现2种方式获取函数地址。GetProcAddress中的hModel实际上是ImageBase,模拟时使用fileBuffer,在函数内部转换。

需要注意一点,名称表里储存的依然是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
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
#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
#define FILEPATH TEXT("D:\\absolution\\temporary\\test.exe")
#define OUTFILEPATH TEXT("D:\\absolution\\temporary\\output.exe")
#define DLLPATH TEXT("D:\\absolution\\temporary\\myDll.dll")
#include<PETool.h>
#pragma comment(lib,"PETool.lib")

DWORD getFunctionAddressByName(LPVOID lpFileBuffer, LPCTSTR checkName) {
PIMAGE_DOS_HEADER pDos =
(PIMAGE_DOS_HEADER)lpFileBuffer;
PIMAGE_NT_HEADERS pNT =
(PIMAGE_NT_HEADERS)((DWORD)lpFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFile =
(PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
PIMAGE_OPTIONAL_HEADER pOption =
(PIMAGE_OPTIONAL_HEADER)((DWORD)pFile + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER pFirstSection =
(PIMAGE_SECTION_HEADER)((DWORD)pOption + pFile->SizeOfOptionalHeader);

PIMAGE_EXPORT_DIRECTORY pExportDir =
(PIMAGE_EXPORT_DIRECTORY)(RVA2FOA(lpFileBuffer, pOption->DataDirectory[0].VirtualAddress) + (DWORD)lpFileBuffer);

/*获取函数地址表、函数名称表、函数序号表的地址*/
PDWORD lpAddOfFns = (PDWORD)(RVA2FOA(lpFileBuffer, (DWORD)pExportDir->AddressOfFunctions) + (DWORD)lpFileBuffer);
PDWORD lpAddOfNames = (PDWORD)(RVA2FOA(lpFileBuffer, (DWORD)pExportDir->AddressOfNames) + (DWORD)lpFileBuffer);
/*序号表的数据为2个字节*/
PWORD lpAddOfOrdinals = (PWORD)(RVA2FOA(lpFileBuffer, (DWORD)pExportDir->AddressOfNameOrdinals) + (DWORD)lpFileBuffer);

DWORD FnAddr = 0;

for (int i = 0; i < pExportDir->NumberOfNames; i++) {
LPCTSTR fnName = (LPCTSTR)(RVA2FOA(lpFileBuffer, lpAddOfNames[i]) + (DWORD)lpFileBuffer);
if (!strcmp(fnName, checkName)) {
/*找到名字后通过下标继续索引序号表*/
DWORD fnOrdinal = lpAddOfOrdinals[i];
FnAddr = lpAddOfFns[fnOrdinal];
return FnAddr;
}
}
}

DWORD getFunctionAddressByOrdinals(LPVOID lpFileBuffer, DWORD checkOrdinal) {
PIMAGE_DOS_HEADER pDos =
(PIMAGE_DOS_HEADER)lpFileBuffer;
PIMAGE_NT_HEADERS pNT =
(PIMAGE_NT_HEADERS)((DWORD)lpFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFile =
(PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
PIMAGE_OPTIONAL_HEADER pOption =
(PIMAGE_OPTIONAL_HEADER)((DWORD)pFile + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER pFirstSection =
(PIMAGE_SECTION_HEADER)((DWORD)pOption + pFile->SizeOfOptionalHeader);

PIMAGE_EXPORT_DIRECTORY pExportDir =
(PIMAGE_EXPORT_DIRECTORY)(RVA2FOA(lpFileBuffer, pOption->DataDirectory[0].VirtualAddress) + (DWORD)lpFileBuffer);

/*获取函数地址表、函数名称表、函数序号表的地址*/
PDWORD lpAddOfFns = (PDWORD)(RVA2FOA(lpFileBuffer, (DWORD)pExportDir->AddressOfFunctions) + (DWORD)lpFileBuffer);
PDWORD lpAddOfNames = (PDWORD)(RVA2FOA(lpFileBuffer, (DWORD)pExportDir->AddressOfNames) + (DWORD)lpFileBuffer);
/*序号表的数据为2个字节*/
PWORD lpAddOfOrdinals = (PWORD)(RVA2FOA(lpFileBuffer, (DWORD)pExportDir->AddressOfNameOrdinals) + (DWORD)lpFileBuffer);

/*导出序号 - Base得到序号表中的序号(地址表下标)*/
DWORD ordinal = checkOrdinal - pExportDir->Base;

/*序号不在地址表下标范围内*/
if (ordinal >= pExportDir->NumberOfFunctions || ordinal < 0) {
return 0;
}

DWORD fnAddr = lpAddOfFns[ordinal];

return fnAddr;


}

int main() {
LPVOID lpFileBuffer = createFileBuffer(DLLPATH);
PIMAGE_DOS_HEADER pDos =
(PIMAGE_DOS_HEADER)lpFileBuffer;
PIMAGE_NT_HEADERS pNT =
(PIMAGE_NT_HEADERS)((DWORD)lpFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFile =
(PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
PIMAGE_OPTIONAL_HEADER pOption =
(PIMAGE_OPTIONAL_HEADER)((DWORD)pFile + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER pFirstSection =
(PIMAGE_SECTION_HEADER)((DWORD)pOption + pFile->SizeOfOptionalHeader);

/*获取导出表的FA(文件绝对地址)
*/
PIMAGE_EXPORT_DIRECTORY pExportDir =
(PIMAGE_EXPORT_DIRECTORY)(RVA2FOA(lpFileBuffer, pOption->DataDirectory[0].VirtualAddress) + (DWORD)lpFileBuffer);

/*获取函数地址表、函数名称表、函数序号表的地址*/
PDWORD lpAddOfFns = (PDWORD)(RVA2FOA(lpFileBuffer, (DWORD)pExportDir->AddressOfFunctions) + (DWORD)lpFileBuffer);
PDWORD lpAddOfNames = (PDWORD)(RVA2FOA(lpFileBuffer, (DWORD)pExportDir->AddressOfNames) + (DWORD)lpFileBuffer);
/*序号表的数据为2个字节*/
PWORD lpAddOfOrdinals = (PWORD)(RVA2FOA(lpFileBuffer, (DWORD)pExportDir->AddressOfNameOrdinals) + (DWORD)lpFileBuffer);

for (int i = 0; i < pExportDir->NumberOfFunctions; i++) {
if (lpAddOfFns[i]) {
/*不为0时才是有效地址,0作为填充下标用*/
printf("地址:0x%X\n", lpAddOfFns[i]);
}
}

for (int i = 0; i < pExportDir->NumberOfNames; i++) {
printf("序号:0x%x\n", lpAddOfOrdinals[i] + pExportDir->Base);
}

for (int i = 0; i < pExportDir->NumberOfNames; i++) {
/*函数名称表的RVA需要转换为FA*/
printf("名称:%s\n", RVA2FOA(lpFileBuffer, (DWORD)lpAddOfNames[i]) + (DWORD)lpFileBuffer);
}

/*测试模拟函数*/

DWORD fnAddr;
fnAddr = getFunctionAddressByName(lpFileBuffer, "div");
if (fnAddr) {
printf("div地址为:0X%X\n", fnAddr);
}
else {
printf("未找到名为\"div\"的函数\n");
}

fnAddr = getFunctionAddressByOrdinals(lpFileBuffer, 7);
if (fnAddr) {
printf("mul地址为:0X%X\n", fnAddr);
}
else {
printf("未找到导出序号为\"7\"的函数\n");
}

free(lpFileBuffer);
getchar();

}