PE第五课——导入表

课堂笔记

上节课讲的是数据目录表,这节课是导入表,导入表好像是数据目录表的一种吧,也不清楚,暂时先这么理解。

现在需要用到库函数已经可以不需要导入表了,可以用 LoadLibarary 或者是 GetProcAddress 来获取函数地址直接使用,但是我们还是有必要学这个。

比较重要的结构体定义如下。

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_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)

DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

然后这次就写了一个输出导入表的一个函数,之前出了一些问题,好像是 LordPE 写了那个 EXE 文件,然后导致拿到的文件头就貌似不太一样,调了很久挺难受,我还一直以为是 RvaToFrva 的问题,因为那里 return 0 啊什么的,后面调试发现 rva 的值很异常才去看那个文件结构的,010一看发现 MS-DOS 头都被改了。

我们找到了 rva 之后呢都要通过那个函数转为在 buffer 的变量。

首先找到数据目录表索引,也就是在扩展头的 DataDirectory 的内容,这其实是一个指针,然后我们通过加一定的偏移找到一些对应的数据目录表,具体偏移用宏定义表示出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor

我们可以看到,1就是导入表的一个偏移。

找到了导入表之后,它有一个虚拟地址,找到虚拟地址之后转 Frva 加上读出的buffer就找到了那个导入表所在的结构体地址啦。它有一个 Name 字段指向名字所在的地址,不过也是虚拟地址,需要转 frva 之后加上 buffer。

当字段为空时,说明已经到了结尾,所以我们循环根据这个条件来。

然后就是一堆的数据结构了,最后我们能找到所有动态链接库的所有导入的函数。

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
#include<windows.h>
#include<stdio.h>
#include"main.h"
int main() {
FILE* fd = fopen("C:\\Users\\xia0ji233\\Desktop\\Home\\C++\\test.exe", "rb");
char* buffer = (char*)malloc(0x100000);
if (buffer == NULL) {
perror("malloc fail");
exit(0);
}
if (fd == NULL) {
perror("NO such File");
exit(0);
}
fread(buffer, 1, 0x100000, fd);
PIMAGE_DOS_HEADER pheader = (PIMAGE_DOS_HEADER)buffer;
printf("MS-DOS INFO:\n");
printf("MAGIC HEADER: ");
fwrite((char*)&pheader->e_magic, 2, 1, stdout);
putchar(10);
printf("PE OFFSET:%x\n", pheader->e_lfanew);
printf("PE INFO:\n");
PIMAGE_NT_HEADERS ReadNTHeaders = (PIMAGE_NT_HEADERS)(&(buffer[pheader->e_lfanew]));

printf("PE Magic Header:");
fwrite(&ReadNTHeaders->Signature, 2, 1, stdout);
putchar(10);

printf("Standard Header info:");
printf("Platform:");
switch (ReadNTHeaders->FileHeader.Machine)
{
case IMAGE_FILE_MACHINE_I386:
printf("I386");
break;
case IMAGE_FILE_MACHINE_IA64:
printf("Intel 64");
break;
case IMAGE_FILE_MACHINE_AMD64:
printf("AMD 64");
break;
default:
printf("UNKnown Platform");
break;
}
putchar(10);

printf("Optional PE Header:");
printf("ImageBase:%08x\n", ReadNTHeaders->OptionalHeader.ImageBase);

PIMAGE_SECTION_HEADER ReadSectionHeader = IMAGE_FIRST_SECTION(ReadNTHeaders);
PIMAGE_FILE_HEADER pFileHeader = &ReadNTHeaders->FileHeader;
for (int i = 0; i < pFileHeader->NumberOfSections; i++) {
printf("Name(区段名称):%s\n", ReadSectionHeader[i].Name);
printf("Voffset(起始的相对虚拟地址):%08X\n", ReadSectionHeader[i].VirtualAddress);
printf("VSize(区段大小):%08X\n", ReadSectionHeader[i].SizeOfRawData);
printf("ROffset(文件偏移):%08X\n", ReadSectionHeader[i].PointerToRawData);
printf("RSize(文件中区段大小):%08X\n", ReadSectionHeader[i].Misc.VirtualSize);
printf("标记(区段的属性):%08X\n\n", ReadSectionHeader[i].Characteristics);
}
ImportTable(buffer);
system("pause");
}

DWORD RvaToFrva(DWORD Rva, char* buffer) {
//DOS
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(buffer + pDos->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
if (Rva < pSection[0].VirtualAddress) {
return Rva;
}
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++) {
if (Rva >= pSection[i].VirtualAddress && Rva <= pSection[i].Misc.VirtualSize + pSection[i].VirtualAddress) {
//Rva-pSection[i].VirtualAddress相当于是数据目录表相对于区段的偏移加上该区段在文件中的位置
//就相当于数据目录表在文件中的位置了。
return Rva - pSection[i].VirtualAddress + pSection[i].PointerToRawData;
}
}
return Rva;
}

void ImportTable(char *buffer) {
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(buffer + pDos->e_lfanew);
PIMAGE_DATA_DIRECTORY pImportDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_IMPORT);

PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(buffer + RvaToFrva(pImportDir->VirtualAddress, buffer));

while (pImport->Name) {
char* DllName = (char*)(RvaToFrva(pImport->Name, buffer) + buffer);
printf("DllName: %s\n", DllName);
printf("日期时间标志:%08X\n", pImport->TimeDateStamp);
printf("ForwarderChain:%08X\n", pImport->ForwarderChain);
printf("名称OFFSET:%08X\n", pImport->Name);
printf("FirstThunk:%08X\n", pImport->FirstThunk);
printf("OriginalFirstThunk:%08X\n\n", pImport->OriginalFirstThunk);

PIMAGE_THUNK_DATA pIat = (PIMAGE_THUNK_DATA)(RvaToFrva(pImport->OriginalFirstThunk, buffer) + buffer);
DWORD index = 0;
DWORD ImportOffset = 0;
while (pIat->u1.Ordinal != 0) {
PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)(RvaToFrva(pIat->u1.AddressOfData, buffer) + buffer);
printf("API 名称:%s\n", pName->Name);
printf("Hint:%04X\n", pName->Hint);
printf("ThunkValue:%08X\n\n" ,pIat->u1.Function);
pIat++;
}

pImport++;
}

}

运行结果