《Windows PE》4.1.3 IAT函数地址表

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表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值