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;
#ifdef WIN64
PIMAGE_NT_HEADERS64 psImageNTHeader;
#else
PIMAGE_NT_HEADERS32 psImageNTHeader;
#endif
PIMAGE_SECTION_HEADER sImageSecctionHeader[20]; //节表项
hFile = CreateFile(szFile,
GENERIC_READ, // 只读打开
FILE_SHARE_READ, // 允许其他进程以读取方式打开文件
NULL, // 默认安全属性
OPEN_EXISTING, // 打开已存在的文件
FILE_ATTRIBUTE_NORMAL, // 普通文件
NULL);
dwPageSize = GetFileSize(hFile, 0);//获得文件大小可通过结构体获取
lpvResult = VirtualAlloc(// 给文件分配内存空间
NULL, // 系统自动选择起始地址
dwPageSize, // 指定要分配的内存大小,以字节为单位
MEM_COMMIT, // 将申请到的虚拟内存提交到物理内存
PAGE_READWRITE); // read/write 属性
//将文件读至分配的内存
bReadFile = ReadFile(hFile, lpvResult, dwPageSize, &dwBytesRead, 0);
//实现将磁盘文件地址偏移至内存地址
#ifdef WIN64
psImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;
psImageNTHeader = (PIMAGE_NT_HEADERS64)
((BYTE*)lpvResult + psImageDOSHeader->e_lfanew);
DWORD num = psImageNTHeader->FileHeader.NumberOfSections;
sImageSecctionHeader[0] = (PIMAGE_SECTION_HEADER)
((BYTE*)psImageNTHeader + sizeof(IMAGE_NT_HEADERS64));
for(DWORD i = 1; i < num;i++)
{
sImageSecctionHeader[i] = (PIMAGE_SECTION_HEADER)
((BYTE*)sImageSecctionHeader[0] + sizeof(IMAGE_SECTION_HEADER));
}
#else
psImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;
psImageNTHeader = (PIMAGE_NT_HEADERS32)
((BYTE*)lpvResult + psImageDOSHeader->e_lfanew);
DWORD num = psImageNTHeader->FileHeader.NumberOfSections;
sImageSecctionHeader[0] = (PIMAGE_SECTION_HEADER)
((BYTE*)psImageNTHeader + sizeof(IMAGE_NT_HEADERS32));
for (DWORD i = 1; i < num; i++)
{
sImageSecctionHeader[i] = (PIMAGE_SECTION_HEADER)
((BYTE*)sImageSecctionHeader[i-1] + sizeof(IMAGE_SECTION_HEADER));
}
#endif
//以4KB为单位分配内存空间
dwPageSize = sImageSecctionHeader[num-1]->Misc.VirtualSize +
sImageSecctionHeader[num-1]->VirtualAddress;
dwPageSize = (dwPageSize / 0x1000 + 1) * 0x1000;//已4KB为单位对齐
lpvResult2 = VirtualAlloc(
NULL,
dwPageSize, // 内存大小, 以字节为单位
MEM_COMMIT, // 将申请到的虚拟内存提交到物理内存
PAGE_READWRITE); // read/write 属性
//拷贝PE文件头
#ifdef WIN64
CopyMemory(lpvResult2, lpvResult, psImageDOSHeader->e_lfanew +
sizeof(IMAGE_NT_HEADERS64) + 3 * sizeof(IMAGE_SECTION_HEADER));
#else
CopyMemory(lpvResult2, lpvResult, psImageDOSHeader->e_lfanew +
sizeof(IMAGE_NT_HEADERS32) + 3 * sizeof(IMAGE_SECTION_HEADER));
#endif
//拷贝第0个节区数据
for (DWORD i = 0;i < num;i++)
{
CopyMemory((BYTE*)lpvResult2 + sImageSecctionHeader[i]->VirtualAddress,
(BYTE*)lpvResult + sImageSecctionHeader[i]->PointerToRawData,
sImageSecctionHeader[i]->Misc.VirtualSize);
}
return (PBYTE)lpvResult2;
}
//32位PE文件
VOID iat32(PBYTE lpvResult)
{
DWORD dwTemp = 0;
PIMAGE_DOS_HEADER pImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;
PIMAGE_NT_HEADERS32 psImageNTHeader = (PIMAGE_NT_HEADERS32)(lpvResult +
pImageDOSHeader->e_lfanew);
//导出表VA地址
dwTemp = psImageNTHeader->OptionalHeader.DataDirectory[12].VirtualAddress +
(DWORD)pImageDOSHeader;
PIMAGE_THUNK_DATA32 IatAddress = (PIMAGE_THUNK_DATA32)dwTemp;
printf("IAT函数地址表的VA地址为%08x\n", (DWORD)IatAddress);
//导出表VA地址
dwTemp = psImageNTHeader->OptionalHeader.DataDirectory[1].VirtualAddress +
(DWORD)pImageDOSHeader;
PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor =
(PIMAGE_IMPORT_DESCRIPTOR)dwTemp;
//遍历导入表描述符
for (DWORD i = 0; *(DWORD*)(pImageImportDescriptor + i) != 0; i++)
{
//打印模块名
DWORD NameAddress = (pImageImportDescriptor + i)->Name +
(DWORD)pImageDOSHeader;
printf("%s:\n", (PBYTE)NameAddress);
//获取桥1 RVA地址
DWORD OriginalFirstThunkRVA = (pImageImportDescriptor +
i)->OriginalFirstThunk;
DWORD TimeDate = (pImageImportDescriptor + i)->TimeDateStamp;
if (TimeDate == -1)
{
printf("已绑定导入函数。\n");
//获取桥2 VA地址
PIMAGE_THUNK_DATA32 FirstThunkData =
(PIMAGE_THUNK_DATA32)(pImageImportDescriptor + i)->FirstThunk;
dwTemp = (DWORD)pImageDOSHeader + (DWORD)FirstThunkData;
FirstThunkData = (PIMAGE_THUNK_DATA32)dwTemp;
for (DWORD j = 0; *((DWORD*)FirstThunkData + j) != 0; j++)
{
//INT表
DWORD AddressOfData = (FirstThunkData + j)->u1.AddressOfData;
//输出函数名
printf("函数地址:0x%04X\n", AddressOfData);
}
printf("\n");
}
else
{
//获取桥2 VA地址
PIMAGE_THUNK_DATA32 FirstThunkData =
(PIMAGE_THUNK_DATA32)(pImageImportDescriptor + i)->FirstThunk;
dwTemp = (DWORD)pImageDOSHeader + (DWORD)FirstThunkData;
FirstThunkData = (PIMAGE_THUNK_DATA32)dwTemp;
for (DWORD j = 0; *((DWORD*)FirstThunkData + j) != 0; j++)
{
//INT表
DWORD AddressOfDataRVA = (FirstThunkData +
j)->u1.AddressOfData;
dwTemp = (DWORD)pImageDOSHeader + (DWORD)AddressOfDataRVA;
PIMAGE_IMPORT_BY_NAME pImageImportByName =
(PIMAGE_IMPORT_BY_NAME)dwTemp;
//输出函数名
printf("0x%04X : %s\n", pImageImportByName->Hint,
pImageImportByName->Name);
}
printf("\n");
}
}
}
//64位PE文件
VOID iat64(PBYTE lpvResult)
{
ULONGLONG dwTemp = 0;
PIMAGE_DOS_HEADER pImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;
PIMAGE_NT_HEADERS64 psImageNTHeader = (PIMAGE_NT_HEADERS64)(lpvResult +
pImageDOSHeader->e_lfanew);
//导出表VA地址
dwTemp = psImageNTHeader->OptionalHeader.DataDirectory[12].VirtualAddress +
(ULONGLONG)pImageDOSHeader;
PIMAGE_THUNK_DATA64 IatAddress = (PIMAGE_THUNK_DATA64)dwTemp;
printf("IAT函数地址表的VA地址为%08lx\n", (unsigned long)IatAddress);
//导出表VA地址
dwTemp = psImageNTHeader->OptionalHeader.DataDirectory[1].VirtualAddress +
(ULONGLONG)pImageDOSHeader;
PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor =
(PIMAGE_IMPORT_DESCRIPTOR)dwTemp;
//遍历导入表描述符
for (DWORD i = 0; *(DWORD*)(pImageImportDescriptor + i) != 0; i++)
{
//打印模块名
LONGLONG NameAddress = (pImageImportDescriptor + i)->Name +
(ULONGLONG)pImageDOSHeader;
printf("%s:\n", (PBYTE)NameAddress);
//获取桥1 RVA地址
DWORD OriginalFirstThunkRVA = (pImageImportDescriptor +
i)->OriginalFirstThunk;
DWORD TimeDate = (pImageImportDescriptor + i)->TimeDateStamp;
if (TimeDate == -1)
{
printf("已绑定导入函数。\n");
//获取桥2 VA地址
PIMAGE_THUNK_DATA64 FirstThunkData =
(PIMAGE_THUNK_DATA64)(pImageImportDescriptor + i)->FirstThunk;
dwTemp = (ULONGLONG)pImageDOSHeader + (ULONGLONG)FirstThunkData;
FirstThunkData = (PIMAGE_THUNK_DATA64)dwTemp;
for (DWORD j = 0; *((DWORD*)FirstThunkData + j) != 0; j++)
{
//INT表
ULONGLONG AddressOfData = (FirstThunkData +
j)->u1.AddressOfData;
//输出函数名
printf("函数地址:0x%08lx\n", (unsigned long)AddressOfData);
}
printf("\n");
}
else
{
//获取桥2 VA地址
PIMAGE_THUNK_DATA64 FirstThunkData =
(PIMAGE_THUNK_DATA64)(pImageImportDescriptor + i)->FirstThunk;
dwTemp = (ULONGLONG)pImageDOSHeader + (ULONGLONG)FirstThunkData;
FirstThunkData = (PIMAGE_THUNK_DATA64)dwTemp;
for (ULONGLONG j = 0; *((ULONGLONG*)FirstThunkData + j) != 0; j++)
{
//INT表
ULONGLONG AddressOfDataRVA = (FirstThunkData +
j)->u1.AddressOfData;
dwTemp = (ULONGLONG)pImageDOSHeader +
(ULONGLONG)AddressOfDataRVA;
PIMAGE_IMPORT_BY_NAME pImageImportByName =
(PIMAGE_IMPORT_BY_NAME)dwTemp;
//输出函数名
printf("0x%04X : %s\n", pImageImportByName->Hint,
pImageImportByName->Name);
}
printf("\n");
}
}
}
//RVA转FOA
DWORD RvaToFoa(PIMAGE_NT_HEADERS ntHeaders, DWORD rva) {
//ntHeaders+4+sizeof(IMAGE_FILE_HEADER)+FileHeader.SizeOfOptionalHeader(32或64位PE)
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
WORD numberOfSections = ntHeaders->FileHeader.NumberOfSections;
for (WORD i = 0; i < numberOfSections; i++) {
DWORD sectionStartRva = sectionHeader->VirtualAddress;
DWORD sectionEndRva = sectionStartRva + sectionHeader->SizeOfRawData;
if (rva >= sectionStartRva && rva < sectionEndRva) {
DWORD foa = sectionHeader->PointerToRawData + (rva –
sectionStartRva);
return foa;
}
sectionHeader++;
}
return 0; // RVA not found
}
总结
1.示例程序以PE内存映像格式遍历IAT函数地址表。
2.遍历时,根据导入表描述符的TimeDateStamp字段考虑两种情况:
已绑定导入表,直接输出导入函数的实际地址,如notepad32.exe。
未绑定导入表,则输出导入函数名。
3.遍历IAT函数地址表的流程:
第一步:创建PE内存映像文件,参见实验19。
第二步:遍历IAT函数地址表。首先找到数据目录项的第12项,获取IAT函数地址表的RVA地址,加上基址后,得到并输出加载到内存中的IAT函数地址表的VA地址。
第三步:根据数据目录项的第1项,找到导入表的RVA地址,加上基址后得到内存映像文件中的导入表VA地址。
第四步:根据导入表描述符,输出DLL模块名。
第五步:判断是否已绑定导入表。如果已绑定,根据FirstThunk字段遍历导入函数地址表。如果未绑定,则根据FirstThunk字段遍历导入函数名。遍历的过程如图4-2所示。
遍历FirstThunk
图4-2 遍历IAT表
注意
IAT表中RVA地址后面4个字节0作为每个DLL模块的分隔符。示例中包含user32.dll和kernel32.dl两个DLL模块,每个模块各有一个导入函数。
实验二十七:在DTDebug调试中观察IAT表
我们以32位汇编版HelloWorld.exe为例,将其拖入DTDebug调试器,按Ctrl+F9进入程序入口地址,然后在内存窗口按Ctrl+G,输入0x00042000,将地址跳转到.rdata节区。如图4-3所示:
图4-3 加载内存中的映像文件
总结
仔细观察调试器反汇编窗口中的.text节区(代码段)。HelloWorld.exe程序非常简单,调用了user32.dll中的MessageBoxA函数和kernel32.dll中的ExitProcess函数。
1.打开内存映射窗口
Memory map
Address Size Owner Section Contains Type Access Initial Mapped as
00040000 00001000 HelloWor PE header Imag R RWE
00041000 00001000 HelloWor .text code Imag R RWE
00042000 00001000 HelloWor .rdata Imag R RWE
00043000 00001000 HelloWor .data data Imag R RWE
00044000 00001000 HelloWor .reloc relocations Imag R RWE
…
74570000 00001000 user32 PE header Imag R RWE
74571000 00082000 user32 .text code,exports Imag R RWE
745F3000 00002000 user32 .data data Imag R RWE
745F5000 00008000 user32 .idata Imag R RWE
745FD000 00001000 user32 .didat Imag R RWE
745FE000 000E2000 user32 .rsrc resources Imag R RWE
746E0000 00006000 user32 .reloc relocations Imag R RWE
…
77170000 00001000 KERNEL32 PE header Imag R RWE
77180000 0005F000 KERNEL32 .text code Imag R E RWE
771E0000 00027000 KERNEL32 .rdata exports Imag R RWE
77210000 00001000 KERNEL32 .data data Imag RW RWE
77220000 00001000 KERNEL32 .rsrc resources Imag R RWE
77230000 00005000 KERNEL32 .reloc relocations Imag R RWE
…
我们需要关注以下几个地址区间:
00041000 00001000 HelloWor .text code Imag R RWE
00042000 00001000 HelloWor .rdata Imag R RWE
74571000 00082000 user32 .text code,exports Imag R RWE
77180000 0005F000 KERNEL32 .text code Imag R E RWE
2.MessageBoxA函数
MessageBoxA函数首先将4个参数push入栈,然后是call指令。对应的硬编码是E8 00000007,跳转7个字节,跳到地址0x0004101A,对应的汇编指令为JMP NEAR DWORD PTR DS:[42008]。注意0x00042008这个地址=0x00040000(基址)+ 2008(RVA地址)。
3.ExitProcess函数
ExitProcess函数push一个参数0,然后是call指令,硬编码为E8 00000006,跳转6个字节,跳到地址0x00041020,对应的汇编指令为JMP NEAR DWORD PTR DS:[42000]。注意0x00042000这个地址=0x00040000(基址)+ 2000(RVA地址)。
4.再观察一下内存窗口,.rdata节区地址0x00042000处的数据为0x77183BE0,就是ExitProcess函数的真实地址,刚好位于KERNEL32.dll的.text节区内。.rdata节区地址0x00042008处的数据为0x745DFDB0,就是MessageBoxA函数的真实地址,刚好位于user32.dll的.text节区内。
00042000 E0 3B 18 77 00 00 00 00 B0 FD 5D 74 00 00 00 00 ?w....褒]t....
0004101A $- FF25 08200400 JMP NEAR DWORD PTR DS:[42008]; user32.MessageBoxA
00041020 .- FF25 00200400 JMP NEAR DWORD PTR DS:[42000]; KERNEL32.ExitProcess
因此上面的两条反汇编语句跳转的地址就是IAT函数地址表中替换后的真实导入函数地址。请读者单步执行上述两条JMP指令,观察一下跳转的函数入口地址。
5.对照一下WinHex内HelloWorld.exe程序.rdata节区内的IAT表
00000600 42 21 00 00 00 00 00 00 28 21 00 00 00 00 00 00 B!......(!......
FOA文件偏移地址00000600对应的VA地址就是0x00042000,ExitProcess函数的RVA地址00002142H在加载到内存时被替换为真实的导入函数地址0x77183BE0。
FOA文件偏移地址00000608对应的VA地址就是0x00042008,MessageBoxA函数的RVA地址00002128H在加载到内存时被替换为真实的导入函数地址0x745DFDB0。
6.PE文件加载前后的变化如下图所示:
图4-4PE文件加载前
图4-5 PE文件加载后
练习
请读者分别使用静态分析和动态分析的方法,分析32位和64位记事本程序的导入表和IAT表。