Last updated on May 9, 2024 pm
导入表工作原理
PE文件加载过程中,OS会先将文件“拉伸”,而后OS会将其“贴”在虚拟内存,此时即可根据导入表内申请的名单来寻找该Module运行所需要的其他支持。
一般来说是因为某个Module运行需要外部提供某些函数的实现支持。
导入表内描述了需要的函数的名称或者函数序号,以及这些函数位于的Module名称(一般是XXX.DLL文件),OS根据依赖的Module名称就可以将该Module也加载到内存中,并检查该Module的导出表,按序号或名称的方式从导出表中找到所需的函数地址。
当函数地址被找到后,OS即可将该函数的绝对地址直接贴回到申请导入的PE文件的导入表中(导入表中的IAT结构就专门干这事,下文会详细探讨)。
当我们联系汇编代码时,就可以想通这个道理:
静态引用的函数地址,例如某程序内部自实现的函数,其地址在汇编代码中表现为绝对地址(ImageBase + RVA): call XXXX
动态引用的函数地址,其地址依赖外部导入,故汇编代码中表现为 call dword ptr [XXXX]。这里的XXXX即为IAT中存储导入函数地址的位置,可以理解为一个函数指针。
导入表位置
1 2 3 4
| typedef struct _PE_HEADERS_OPTIONAL { ... PE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBERSOF_DIRECTORY_ENTRY]; }PEHOPTIONAL, *lpPEHOPTIONAL;
|
导入表位于OP头的DataDirectory数组的第2个元素,即DataDirectory[1] 的位置,存储导入表地址。
还记得导出表在哪里吗?正是 DataDirectory[0] 的位置。
导入表结构
1 2 3 4 5 6 7 8 9 10
| typedef struct _DATA_IMPORT_DIRECTORY { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } DATA_IMPORT_DIRECTORY, * PDATA_IMPORT_DIRECTORY;
|
一个PE程序会依赖多少外部的DLL ? 不知道,可能是一个,也可能是多个。
因此导入表注定不会只有一张,往往会有多张。
通过结构体的联合体成员Characteristics是否等于0,即可判断是否已遍历所有的导入表。
其实可以将导入表的入口地址,解释为一个存储了DATA_IMPORT_DIRECTORY结构的数组成员。
INT与IAT
INT,即Import Name Table,直译为导入名称表。
IAT,即Import Address Table,直译为导入地址表。
DATA_IMPORT_DIRECTORY结构的OriginalFirstThunk指向INT,FirstThunk成员指向IAT。
需要注意的是,IAT表也可以通过OP头的DataDirectory数组来找到,它位于DataDirectory数组的第13个元素,即DataDirectory[12]。
INT表与IAT结构相同:
1 2 3 4 5 6 7 8
| typedef struct _DATA_THUNK_DATA { union { DWORD ForwarderString; DWORD Function; DWORD Ordinal; DWORD AddressOfData; }; } DATA_THUNK_DATA, * PDATA_THUNK_DATA;
|
看似复杂的结构,其实整个结构都是联合体,一般来说直接将其视为四字节整型DWORD即可。
当该值为0时,即到达末尾结束,因此可将INT和IAT都看作一个四字节整型数组,即DWORD[]。
函数名称 or 函数序号
我们通过AddressOfData来获取数据。
AddressOfData为一个32bit的整型, 解析该值时需分两种情况:
- 该值最高位为0,那么该值实际上是一个RVA,指向NAME结构,通过该结构的Name成员即可获取函数名称:
1 2 3 4
| typedef struct _DATA_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } DATA_IMPORT_BY_NAME, * PDATA_IMPORT_BY_NAME;
|
- 该值最高位为1,那么该值是一个函数序号,去掉最高位后剩下的31位即为函数序号。
PE加载前后的不同之处
在PE文件加载前,INT和IAT两张表中存储的内容完全一样,都是函数的序号或函数名称。
而当PE文件加载完成之后,即OS已经将全部所需的导入函数找到后,IAT表中存储的不再是函数名称或序号,而是导入函数的绝对地址。
即AddressOfData存储的内容,已经变成一个绝对地址,该地址即导入函数的入口地址。
此时INT表与IAT互为对照,INT内存储名字,IAT内存储地址。
编码实现打印导入表
主函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void work06() { PEFILE peFile{ 0 }; FILE* pFile = fopen("D:\\1.exe", "rb");
int fileLength = LoadFileToMemory(pFile, &peFile); int r = fclose(pFile);
if (peFile.h_pe == nullptr) { return; }
PDATA_IMPORT_DIRECTORY pImportTable = nullptr; GetImportTable(&pImportTable, &peFile);
PrintImportTable(pImportTable, &peFile); }
|
打印导入表
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
| void PrintImportTable(PDATA_IMPORT_DIRECTORY pImportTable, lpPEFILE lpPeFile) { DWORD imageBase = (DWORD)lpPeFile->h_dos;
while (pImportTable->Characteristics != NULL) { char* dllName = (char*)Rva2Foa(pImportTable->Name, lpPeFile) + imageBase; printf(" %s : \n OriginalFirstThunk :0x%x \n FirstThunk : 0x%x \n", dllName, pImportTable->OriginalFirstThunk, pImportTable->FirstThunk);
PDATA_THUNK_DATA pImportNameTable = (PDATA_THUNK_DATA)(imageBase + Rva2Foa(pImportTable->OriginalFirstThunk, lpPeFile)); printf("INT:\n"); PrintThunkData(pImportNameTable, lpPeFile); PDATA_THUNK_DATA pImportAddressTable = (PDATA_THUNK_DATA)(imageBase + Rva2Foa(pImportTable->FirstThunk, lpPeFile)); printf("IAT:\n"); PrintThunkData(pImportNameTable, lpPeFile);
for (int i = 0; i < 10; i++) { printf("**********"); } printf("\n"); pImportTable++; } }
|
打印IAT或INT表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void PrintThunkData(PDATA_THUNK_DATA thunkData, lpPEFILE lpPeFile) { printf("%8s\t%30s\t%10s\t%10s\t\n", "Adress:", "Name:", "Hint:", "OrderNo:");
DWORD imageBase = (DWORD)lpPeFile->h_dos; DWORD AddressOfData = thunkData->AddressOfData; while (AddressOfData != NULL) { DWORD orderNo = AddressOfData >> 31;
if (orderNo == 1) { orderNo = AddressOfData & 0x7FFFFFFF; printf("%8x\t%30s\t%10s\t%10d \n", AddressOfData, "Null", "Null", orderNo); } else { PDATA_IMPORT_BY_NAME name = (PDATA_IMPORT_BY_NAME)(Rva2Foa(AddressOfData, lpPeFile) + imageBase); printf("%8x\t%30s\t%10d\t%10d \n", AddressOfData, name->Name, name->Hint, orderNo); }
thunkData++; AddressOfData = thunkData->AddressOfData; } }
|