导入表解析

导入表结构

在程序加载前INT和IAT内容一致指向同样的结构体,但是在程序加载后INT不变但是IAT会变PE加载器填充为函数地址。

image-20230824151520040

image-20230824202648524

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
// 导入表VirtualAddress指向的导入表
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //(1) 指向导入名称表(INT)的RVA*
};
DWORD TimeDateStamp; // (2) 时间标识
DWORD ForwarderChain; // (3) 转发链,如果不转发则此值为0
DWORD Name; // (4) 指向导入映像文件的名字*
DWORD FirstThunk; // (5) 指向导入地址表(IAT)的RVA*
} IMAGE_IMPORT_DESCRIPTOR;

// INT/未加载前的IAT 导入名称/未加载钱的地址表
typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString; // (1) 转发字符串的RVA
PDWORD Function; // (2) 被导入函数的地址
DWORD Ordinal; // (3) 被导入函数的序号
PIMAGE_IMPORT_BY_NAME AddressOfData; // (4) 名称导入的表 PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;

// 名称导入的表 PIMAGE_IMPORT_BY_NAME
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // (1) 需导入的函数序号
BYTE Name[1];// (2) 需导入的函数名称
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

导入表_IMAGE_IMPORT_DESCRIPTOR中主要包含了

  1. Name导入模块的名称
  2. OriginalFirstThunk记录函数名称的IAT表(加载前和INT相同)
  3. FirstThunk记录函数地址的INT表

完整解析导入表

PE -> DOS -> NT -> OPT -> section[1]导入表 -> IAT/INT -> PIMAGE_IMPORT_BY_NAME、Address

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

#define PATH L"C:\\Test.dll"

bool IsPeFile(LPVOID lpFileBuffer)
{
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)lpFileBuffer;
if (IMAGE_DOS_SIGNATURE == dosHeader->e_magic)
{
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((LONG)lpFileBuffer + dosHeader->e_lfanew);
if (IMAGE_NT_SIGNATURE == ntHeader->Signature) return true;
}
return false;
}

DWORD RvaToFoa(DWORD rva, LPVOID lpFileBuffer)
{
// 遍历RVA所属哪个区段表,计算段内偏移加上段起始FOA,相加得到FOA
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)lpFileBuffer;
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((LONG)lpFileBuffer + dosHeader->e_lfanew);
PIMAGE_FILE_HEADER fileHeader = (PIMAGE_FILE_HEADER)&ntHeader->FileHeader;
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeader);

// 遍历区段表
for (int i = 0; i < fileHeader->NumberOfSections; i++)
{
if (sectionHeader[i].VirtualAddress <= rva &&
rva <= sectionHeader[i].SizeOfRawData + sectionHeader[i].VirtualAddress)
{
DWORD foa = rva - sectionHeader[i].VirtualAddress + sectionHeader[i].PointerToRawData;
return foa;
}
}
return 0;
}

// 解析导入表
void ParseImportTable(LPVOID lpFileBuffer)
{
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)lpFileBuffer;
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((LONG)lpFileBuffer + dosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER optHeader = (PIMAGE_OPTIONAL_HEADER)&ntHeader->OptionalHeader;

// 读取的文件,RVA转成FOA来解析导入表,并不是加载PE,不会进行内存展开。
DWORD importTableRva = optHeader->DataDirectory[1].VirtualAddress;
DWORD importTableFoa = RvaToFoa(importTableRva, lpFileBuffer);
PIMAGE_IMPORT_DESCRIPTOR importTable = (PIMAGE_IMPORT_DESCRIPTOR)(importTableFoa + (DWORD)lpFileBuffer);

// 导入表是一个不定长的数组,以 20个全 0 为结尾
while (importTable->Name != 0)
{
// 获取到导入模块的名称
DWORD moduleNameFoa = RvaToFoa(importTable->Name, lpFileBuffer);
PCHAR name = (PCHAR)(moduleNameFoa + (DWORD)lpFileBuffer);
printf("导入的模块名称为: %s\n", name);

// 通过FirstThunk成员字段获取 IAT
DWORD iatFoa = RvaToFoa(importTable->FirstThunk, lpFileBuffer);
PIMAGE_THUNK_DATA iat = (PIMAGE_THUNK_DATA)(iatFoa + (DWORD)lpFileBuffer);

while (iat->u1.AddressOfData != 0) // 序号表(函数名称、函数序号)
{
// 判断是否仅以序号导入的函数
// 如果IMAGE_THUNK_DATA 最高位为1 则是按序号导入,否则就是按名称导入的。
bool isOnlyOrdinal = IMAGE_SNAP_BY_ORDINAL(iat->u1.Ordinal);
if (isOnlyOrdinal == false)
{
DWORD importByNameTableRva = RvaToFoa(iat->u1.AddressOfData, lpFileBuffer);
PIMAGE_IMPORT_BY_NAME nameTable = (PIMAGE_IMPORT_BY_NAME)(importByNameTableRva + (DWORD)lpFileBuffer);

printf("导入函数序号为: 0x%02X , 函数名称为: %s \n", nameTable->Hint, nameTable->Name);
}
else // 仅以序号导入,最高位为1,剩余部分为函数序号
{
printf("函数的导入序号为: %x\n", iat->u1.Ordinal & 0x7FFFFFFF);
}

iat++;
}
importTable++;
}
}

int main()
{
// 打开文件,获取文件句柄。
HANDLE hFile = CreateFile(
PATH,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("打开文件失败");
system("pause");
return 0;
}
// 获取文件大小,申请堆空间,将读取到的内容放进去。
DWORD real_size = 0;
DWORD file_size = GetFileSize(hFile, NULL);

char* pFileBuffer = new char[file_size];
ReadFile(hFile, pFileBuffer, file_size, &real_size, NULL);
CloseHandle(hFile); // 关闭文件句柄,防止资源泄露

// 判断PE文件,解析导入表
if (IsPeFile(pFileBuffer) == true)
{
printf("是有效的PE文件\n");
ParseImportTable(pFileBuffer);
}
else
{
printf("不是有效的PE文件\n");

}
system("pause");
return 0;
}

image-20230824200541739

导出表解析

导出表结构

name -> ordinal -> address / address -> ordinal -> name // 序号表用作名称和地址转换和函数标识

image-20230824195916346

上述已经解析过PE头,这里直接就展示核心函数,解析导出表。

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
void get_export_information(LPVOID fileBuff)
{
// 获取到导出表RVA -> FOA
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileBuff;
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((LONG)fileBuff + dosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER optHeader = (PIMAGE_OPTIONAL_HEADER)&ntHeader->OptionalHeader;

DWORD exportTableRva = optHeader->DataDirectory[0].VirtualAddress;
DWORD exportTableFoa = RvaToFoa(exportTableRva, fileBuff);

PIMAGE_EXPORT_DIRECTORY exportTable = (PIMAGE_EXPORT_DIRECTORY)((DWORD)fileBuff + exportTableFoa);

// 打印导出表模块名称
DWORD exportTableModuleName = RvaToFoa(exportTable->Name, fileBuff);
printf("模块的名称为:%s\n", exportTableModuleName + (DWORD)fileBuff);

// 获取到base基数,序数减去这个基数就是函数地址数组的索引值
DWORD base = exportTable->Base;

// 获取函数序号表位置
DWORD exportOrdinalTableRva = RvaToFoa(exportTable->AddressOfNameOrdinals, fileBuff);
PWORD ordinalTable = (PWORD)(exportOrdinalTableRva + (DWORD)fileBuff);

// 获取函数名称表位置
DWORD exportNameTableRva = RvaToFoa(exportTable->AddressOfNames, fileBuff);
PDWORD nameTable = (PDWORD)(exportNameTableRva + (DWORD)fileBuff);

// 获取函数地址表位置
DWORD exportAddressTable = RvaToFoa(exportTable->AddressOfFunctions, fileBuff);
PDWORD addressTable = (PDWORD)(exportAddressTable + (DWORD)fileBuff);

// 循环遍历三张表(这里使用的导出函数总数,名称表数量只有名称导出函数,没有序号函数)
for (int index = 0; index < exportTable->NumberOfFunctions; index++)
{
int i = 0;
bool isNameExport = false;

for (; i < exportTable->NumberOfNames; i++) // 循环判断序号导出还是名称导出
{
if (index == ordinalTable[i]) // 用名称导出的函数数量,循环自增下标作为索引在序号表里边找
// 用数组下标在序号表里边找序号,如果找到序号就说明
{
isNameExport = true; // 是名称导出
break;
}
}
if (isNameExport) // 名称导出
{
DWORD nameRva = nameTable[i];
DWORD nameFoa = RvaToFoa(nameRva, fileBuff);
printf("函数名称为:%s\n", nameFoa + (DWORD)fileBuff);
}
else if (addressTable[index] != 0) // 序号导出
{
printf("函数名字为 NULL, 地址RVA为:0x%08X\n", addressTable[index]);
}
}
}

三个表之间的寻找对应关系如下图所示:

img

参考文章

https://blog.csdn.net/Apollon_krj/article/details/77417063

https://blog.51cto.com/u_15744744/6121082

《加密与解密》