IAT(Import Address Table)表又称为函数地址表,是Windows可执行文件中的一个重要数据结构,用于存储导入函数的实际入口地址。
在可执行文件中,当一个模块需要调用另一个模块中的函数时,通常会使用导入函数的方式进行。导入函数的入口地址在程序加载时并不确定,需要在运行时进行动态链接,以获取实际的函数入口地址。IAT表就是用来存储这些实际的函数入口地址。
IAT表是一个由函数指针构成的表格,每个函数指针对应一个导入函数。在Windows可执行文件中,IAT表是一个以NULL结尾的函数指针数组,每个函数指针对应一个导入函数的实际入口地址。
IAT表通常是通过导入描述符(Import Descriptor)中的FirstThunk字段指向的地址来访问的。FirstThunk指向一个由IMAGE_THUNK_DATA结构体构成的表,每个IMAGE_THUNK_DATA结构体中的Function字段存储着导入函数的实际入口地址。
在程序加载时,操作系统会根据导入描述符中的INT表和IAT表进行动态链接,将导入函数的实际入口地址填充到IAT表中的函数指针对应的位置。这样,在程序运行时,就可以直接通过IAT表中的函数指针调用导入函数,而无需进行额外的解析和跳转。
注意
IAT函数地址表中的函数指针是在程序加载和链接时被填充的,而导入表描述符中的IAT表中的函数指针在未绑定的状态下与INT表完全相同。如果导入表描述符的TimeDateStamp和ForwarderChain字段是一个特殊的标志值,如0xFFFFFFFF,表示导入函数已绑定,则IAT函数地址表中存储的是真实的导入函数地址。
实验二十五:如何定位IAT表?
我们以32位汇编版HelloWorld.exe为例,将其拖入WinHex,如下所示:
00000140 00 00 00 00 00 00 00 00 DC 20 00 00 3C 00 00 00 ........?..<...
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000160 00 00 00 00 00 00 00 00 00 40 00 00 10 00 00 00 .........@......
00000170 10 20 00 00 1C 00 00 00 00 00 00 00 00 00 00 00 . ..............
00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001A0 00 20 00 00 10 00 00 00 00 00 00 00 00 00 00 00 . ..............
000001B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001C0 2E 74 65 78 74 00 00 00 26 00 00 00 00 10 00 00 .text...&.......
000001D0 00 02 00 00 00 04 00 00 00 00 00 00 00 00 00 00 ................
000001E0 00 00 00 00 20 00 00 60 2E 72 64 61 74 61 00 00 .... ..`.rdata..
000001F0 5E 01 00 00 00 20 00 00 00 02 00 00 00 06 00 00 ^.... ..........
00000200 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@
00000210 2E 64 61 74 61 00 00 00 1B 00 00 00 00 30 00 00 .data........0..
00000220 00 02 00 00 00 08 00 00 00 00 00 00 00 00 00 00 ................
00000230 00 00 00 00 40 00 00 C0 2E 72 65 6C 6F 63 00 00 ....@..?reloc..
00000240 10 00 00 00 00 40 00 00 00 02 00 00 00 0A 00 00 .....@..........
00000250 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 42 ............@..B
首先我们查找数据目录项的第12项,即IAT表项。位于00000220H地址处的RVA值为00002010H,大小为0000001CH。
此RVA地址同样位于.rdata节区。
IAT表的FOA地址=00002000H-00002000H+600H=600H。
00000600 42 21 00 00 00 00 00 00 28 21 00 00 00 00 00 00 B!......(!......
00000610 00 00 00 00 E4 68 03 66 00 00 00 00 0D 00 00 00 ....鋒.f........
00000620 B0 00 00 00 2C 20 00 00 2C 06 00 00 00 00 00 00 ?.., ..,.......
00000630 00 10 00 00 26 00 00 00 2E 74 65 78 74 00 00 00 ....&....text...
00000640 00 20 00 00 10 00 00 00 2E 69 64 61 74 61 24 35 . .......idata$5
00000650 00 00 00 00 10 20 00 00 1C 00 00 00 2E 72 64 61 ..... .......rda
00000660 74 61 00 00 2C 20 00 00 B0 00 00 00 2E 72 64 61 ta.., ..?...rda
00000670 74 61 24 7A 7A 7A 64 62 67 00 00 00 DC 20 00 00 ta$zzzdbg...?..
00000680 28 00 00 00 2E 69 64 61 74 61 24 32 00 00 00 00 (....idata$2....
00000690 04 21 00 00 14 00 00 00 2E 69 64 61 74 61 24 33 .!.......idata$3
000006A0 00 00 00 00 18 21 00 00 10 00 00 00 2E 69 64 61 .....!.......ida
000006B0 74 61 24 34 00 00 00 00 28 21 00 00 36 00 00 00 ta$4....(!..6...
000006C0 2E 69 64 61 74 61 24 36 00 00 00 00 00 30 00 00 .idata$6.....0..
000006D0 1B 00 00 00 2E 64 61 74 61 00 00 00 20 21 00 00 .....data... !..
000006E0 00 00 00 00 00 00 00 00 36 21 00 00 08 20 00 00 ........6!... ..
000006F0 18 21 00 00 00 00 00 00 00 00 00 00 50 21 00 00 .!..........P!..
00000700 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 . ..............
00000710 00 00 00 00 00 00 00 00 42 21 00 00 00 00 00 00 ........B!......
00000720 28 21 00 00 00 00 00 00 B1 01 4D 65 73 73 61 67 (!......?Messag
00000730 65 42 6F 78 41 00 75 73 65 72 33 32 2E 64 6C 6C eBoxA.user32.dll
00000740 00 00 9B 00 45 78 69 74 50 72 6F 63 65 73 73 00 ..?ExitProcess.
00000750 6B 65 72 6E 65 6C 33 32 2E 64 6C 6C 00 00 00 00 kernel32.dll....
IAT表内包含两个函数名的RVA地址:
第一个:00002142H转为FOA地址为742H,是kernel32.dll中的ExitProcess函数。
第二个:00002128H转为FOA地址为728H,是user32.dll中的MessagBoxA函数。
我们再来看一下导入表描述符中的桥2(FirstThunk字段),指向IAT表。
第一个导入表描述符FirstThunk字段的值为00002008H,转为FOA地址为608H,其RVA值为00002128H,即user32.dll中的MessagBoxA函数。
第二个导入表描述符FirstThunk字段的值为00002000H,转为FOA地址为600H,其RVA值为00002142H,即kernel32.dll中的ExitProcess函数。
总结
我们会发现,在未绑定导入函数的情况下,实验二十五中给出的IAT函数地址表中数据与INT表中的数据完全相同。如果已绑定导入函数,则IAT表直接存储真实的函数入口地址。我们将在下一节中详细讲述。
实验二十六:遍历IAT函数地址表。
我们以32位和64位记事本程序为例:
/*------------------------------------------------------------------------
FileName:PrintImportDescriptor.c
实验26:遍历IAT表(支持32位和64位PE)
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <stdio.h>
#include <windows.h>
#define WIN64
PBYTE loadPE(LPCWSTR szFile);
VOID iat32(PBYTE lpvResult);
VOID iat64(PBYTE lpvResult);
DWORD RvaToFoa(PIMAGE_NT_HEADERS ntHeaders, DWORD rva);
int main(int argc, char* argv[])
{
LPCWSTR szFileName = TEXT("c:\\notepad64.exe");
//PCWSTR szFileName = TEXT("c:\\HelloWorld.exe");
PBYTE lpAddress = NULL; //PE文件内存映射文件地址
lpAddress = loadPE(szFileName);
if (lpAddress)
{
printf("%ls\n", szFileName);
#ifdef WIN64
iat64(lpAddress);
#else
iat32(lpAddress);
#endif
}
system("pause");
return 0;
}
//创建PE文件映射对象
PBYTE loadPE(LPCWSTR szFile)
{
HANDLE hFile;
LPVOID lpvResult, lpvResult2;
char buffer[16] = { 0 };
DWORD dwPageSize;
DWORD dwBytesRead = 0;
BOOL bReadFile;
PIMAGE_DOS_HEADER psImageDOSHeader;